migrate resizable onto gpui-base
This commit is contained in:
@@ -7,12 +7,13 @@ use gpui::{
|
|||||||
MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _,
|
MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _,
|
||||||
WeakEntity, Window, div, px,
|
WeakEntity, Window, div, px,
|
||||||
};
|
};
|
||||||
|
use gpui_base::Side;
|
||||||
|
|
||||||
use super::{DockArea, DockItem};
|
use super::{DockArea, DockItem};
|
||||||
use crate::StyledExt;
|
use crate::StyledExt;
|
||||||
use crate::dock::panel::PanelView;
|
use crate::dock::panel::PanelView;
|
||||||
use crate::dock::tab_panel::TabPanel;
|
use crate::dock::tab_panel::TabPanel;
|
||||||
use crate::resizable::{PANEL_MIN_SIZE, resize_handle};
|
use crate::resizable::{PANEL_MIN_SIZE, resize_handle, resize_handle_appearance};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ResizePanel;
|
struct ResizePanel;
|
||||||
@@ -51,6 +52,16 @@ impl DockPlacement {
|
|||||||
pub fn is_right(&self) -> bool {
|
pub fn is_right(&self) -> bool {
|
||||||
matches!(self, Self::Right)
|
matches!(self, Self::Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Base positions the handle against the window edge for the left dock only,
|
||||||
|
/// so every other placement is the same to it as the right one.
|
||||||
|
fn side(&self) -> Side {
|
||||||
|
if self.is_left() {
|
||||||
|
Side::Left
|
||||||
|
} else {
|
||||||
|
Side::Right
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Dock is a fixed container that places at left, bottom, right of the Windows.
|
/// The Dock is a fixed container that places at left, bottom, right of the Windows.
|
||||||
@@ -242,7 +253,8 @@ impl Dock {
|
|||||||
let view = cx.entity().clone();
|
let view = cx.entity().clone();
|
||||||
|
|
||||||
resize_handle("resize-handle", axis)
|
resize_handle("resize-handle", axis)
|
||||||
.placement(self.placement)
|
.placement(self.placement.side())
|
||||||
|
.with_appearance(resize_handle_appearance())
|
||||||
.on_drag(ResizePanel {}, move |info, _, _, cx| {
|
.on_drag(ResizePanel {}, move |info, _, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
view.update(cx, |view, _cx| {
|
view.update(cx, |view, _cx| {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use crate::dock::panel::{Panel, PanelView};
|
|||||||
use crate::dock::tab_panel::TabPanel;
|
use crate::dock::tab_panel::TabPanel;
|
||||||
use crate::h_flex;
|
use crate::h_flex;
|
||||||
use crate::resizable::{
|
use crate::resizable::{
|
||||||
PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState, ResizableState,
|
PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizableState, resizable_panel,
|
||||||
resizable_panel,
|
resize_handle_appearance,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct StackPanel {
|
pub struct StackPanel {
|
||||||
@@ -267,7 +267,7 @@ impl StackPanel {
|
|||||||
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
||||||
self.panels[ix] = Arc::new(new_panel.clone());
|
self.panels[ix] = Arc::new(new_panel.clone());
|
||||||
self.state.update(cx, |state, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
state.replace_panel(ix, ResizablePanelState::default(), cx);
|
state.reset_panel(ix, cx);
|
||||||
});
|
});
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
}
|
}
|
||||||
@@ -376,6 +376,7 @@ impl Render for StackPanel {
|
|||||||
h_flex().size_full().overflow_hidden().child(
|
h_flex().size_full().overflow_hidden().child(
|
||||||
ResizablePanelGroup::new("stack-panel-group")
|
ResizablePanelGroup::new("stack-panel-group")
|
||||||
.with_state(&self.state)
|
.with_state(&self.state)
|
||||||
|
.with_handle_appearance(resize_handle_appearance())
|
||||||
.axis(self.axis)
|
.axis(self.axis)
|
||||||
.children(self.panels.clone().into_iter().map(|panel| {
|
.children(self.panels.clone().into_iter().map(|panel| {
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
|
|||||||
+31
-288
@@ -1,294 +1,37 @@
|
|||||||
use std::ops::Range;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::prelude::FluentBuilder as _;
|
||||||
Along, App, Axis, Bounds, Context, ElementId, EventEmitter, IsZero, Pixels, Window, px,
|
use gpui::{App, InteractiveElement as _, IntoElement, Pixels, Styled as _, Window, div, px};
|
||||||
|
pub(crate) use gpui_base::{PANEL_MIN_SIZE, resize_handle};
|
||||||
|
pub use gpui_base::{
|
||||||
|
ResizablePanel, ResizablePanelEvent, ResizablePanelGroup, ResizableState, h_resizable,
|
||||||
|
resizable_panel, v_resizable,
|
||||||
};
|
};
|
||||||
|
use gpui_base::{ResizeHandleContext, ResizeHandleRenderer};
|
||||||
|
use theme::{ActiveTheme as _, AxisExt as _};
|
||||||
|
|
||||||
mod panel;
|
const HANDLE_SIZE: Pixels = px(1.);
|
||||||
mod resize_handle;
|
|
||||||
pub use panel::*;
|
|
||||||
pub(crate) use resize_handle::*;
|
|
||||||
|
|
||||||
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
/// The coop divider: a 1px line that appears only while the handle is hovered
|
||||||
|
/// or dragged, tinted from the coop palette. Base's built-in line is always
|
||||||
|
/// painted and takes its colours from `ResizableTheme`.
|
||||||
|
pub(crate) fn resize_handle_appearance() -> ResizeHandleRenderer {
|
||||||
|
Rc::new(
|
||||||
|
|context: &ResizeHandleContext, _: &mut Window, cx: &mut App| {
|
||||||
|
let color = if context.is_active() {
|
||||||
|
cx.theme().border_selected
|
||||||
|
} else {
|
||||||
|
cx.theme().border
|
||||||
|
};
|
||||||
|
let axis = context.axis();
|
||||||
|
|
||||||
/// Create a [`ResizablePanelGroup`] with horizontal resizing
|
Some(
|
||||||
pub fn h_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
|
div()
|
||||||
ResizablePanelGroup::new(id).axis(Axis::Horizontal)
|
.group_hover("handle", move |this| this.bg(color))
|
||||||
}
|
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
|
||||||
|
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE))
|
||||||
/// Create a [`ResizablePanelGroup`] with vertical resizing
|
.into_any_element(),
|
||||||
pub fn v_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
|
)
|
||||||
ResizablePanelGroup::new(id).axis(Axis::Vertical)
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
/// Create a [`ResizablePanel`].
|
|
||||||
pub fn resizable_panel() -> ResizablePanel {
|
|
||||||
ResizablePanel::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State for a [`ResizablePanel`]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ResizableState {
|
|
||||||
/// The `axis` will sync to actual axis of the ResizablePanelGroup in use.
|
|
||||||
axis: Axis,
|
|
||||||
panels: Vec<ResizablePanelState>,
|
|
||||||
sizes: Vec<Pixels>,
|
|
||||||
pub(crate) resizing_panel_ix: Option<usize>,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ResizableState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
axis: Axis::Horizontal,
|
|
||||||
panels: vec![],
|
|
||||||
sizes: vec![],
|
|
||||||
resizing_panel_ix: None,
|
|
||||||
bounds: Bounds::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResizableState {
|
|
||||||
/// Get the size of the panels.
|
|
||||||
pub fn sizes(&self) -> &Vec<Pixels> {
|
|
||||||
&self.sizes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn insert_panel(
|
|
||||||
&mut self,
|
|
||||||
size: Option<Pixels>,
|
|
||||||
ix: Option<usize>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let panel_state = ResizablePanelState {
|
|
||||||
size,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = size.unwrap_or(PANEL_MIN_SIZE);
|
|
||||||
|
|
||||||
// We make sure that the size always sums up to the container size
|
|
||||||
// by reducing the size of all other panels first.
|
|
||||||
let container_size = self.container_size().max(px(1.));
|
|
||||||
let total_leftover_size = (container_size - size).max(px(1.));
|
|
||||||
|
|
||||||
for (i, panel) in self.panels.iter_mut().enumerate() {
|
|
||||||
let ratio = self.sizes[i] / container_size;
|
|
||||||
self.sizes[i] = total_leftover_size * ratio;
|
|
||||||
panel.size = Some(self.sizes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ix) = ix {
|
|
||||||
self.panels.insert(ix, panel_state);
|
|
||||||
self.sizes.insert(ix, size);
|
|
||||||
} else {
|
|
||||||
self.panels.push(panel_state);
|
|
||||||
self.sizes.push(size);
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn sync_panels_count(
|
|
||||||
&mut self,
|
|
||||||
axis: Axis,
|
|
||||||
panels_count: usize,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let mut changed = self.axis != axis;
|
|
||||||
self.axis = axis;
|
|
||||||
|
|
||||||
if panels_count > self.panels.len() {
|
|
||||||
let diff = panels_count - self.panels.len();
|
|
||||||
self.panels
|
|
||||||
.extend(vec![ResizablePanelState::default(); diff]);
|
|
||||||
self.sizes.extend(vec![PANEL_MIN_SIZE; diff]);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if panels_count < self.panels.len() {
|
|
||||||
self.panels.truncate(panels_count);
|
|
||||||
self.sizes.truncate(panels_count);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
// We need to make sure the total size is in line with the container size.
|
|
||||||
self.adjust_to_container_size(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn update_panel_size(
|
|
||||||
&mut self,
|
|
||||||
panel_ix: usize,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
size_range: Range<Pixels>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let size = bounds.size.along(self.axis);
|
|
||||||
// This check is only necessary to stop the very first panel from resizing on its own
|
|
||||||
// it needs to be passed when the panel is freshly created so we get the initial size,
|
|
||||||
// but its also fine when it sometimes passes later.
|
|
||||||
if self.sizes[panel_ix].to_f64() == PANEL_MIN_SIZE.to_f64() {
|
|
||||||
self.sizes[panel_ix] = size;
|
|
||||||
self.panels[panel_ix].size = Some(size);
|
|
||||||
}
|
|
||||||
self.panels[panel_ix].bounds = bounds;
|
|
||||||
self.panels[panel_ix].size_range = size_range;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context<Self>) {
|
|
||||||
self.panels.remove(panel_ix);
|
|
||||||
self.sizes.remove(panel_ix);
|
|
||||||
if let Some(resizing_panel_ix) = self.resizing_panel_ix
|
|
||||||
&& resizing_panel_ix > panel_ix
|
|
||||||
{
|
|
||||||
self.resizing_panel_ix = Some(resizing_panel_ix - 1);
|
|
||||||
}
|
|
||||||
self.adjust_to_container_size(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn replace_panel(
|
|
||||||
&mut self,
|
|
||||||
panel_ix: usize,
|
|
||||||
panel: ResizablePanelState,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let old_size = self.sizes[panel_ix];
|
|
||||||
|
|
||||||
self.panels[panel_ix] = panel;
|
|
||||||
self.sizes[panel_ix] = old_size;
|
|
||||||
self.adjust_to_container_size(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn clear(&mut self) {
|
|
||||||
self.panels.clear();
|
|
||||||
self.sizes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn container_size(&self) -> Pixels {
|
|
||||||
self.bounds.size.along(self.axis)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn done_resizing(&mut self, cx: &mut Context<Self>) {
|
|
||||||
self.resizing_panel_ix = None;
|
|
||||||
cx.emit(ResizablePanelEvent::Resized);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn panel_size_range(&self, ix: usize) -> Range<Pixels> {
|
|
||||||
let Some(panel) = self.panels.get(ix) else {
|
|
||||||
return PANEL_MIN_SIZE..Pixels::MAX;
|
|
||||||
};
|
|
||||||
|
|
||||||
panel.size_range.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_real_panel_sizes(&mut self, _: &App) {
|
|
||||||
for (i, panel) in self.panels.iter().enumerate() {
|
|
||||||
self.sizes[i] = panel.bounds.size.along(self.axis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `ix`` is the index of the panel to resize,
|
|
||||||
/// and the `size` is the new size for the panel.
|
|
||||||
fn resize_panel(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let old_sizes = self.sizes.clone();
|
|
||||||
|
|
||||||
let mut ix = ix;
|
|
||||||
// Only resize the left panels.
|
|
||||||
if ix >= old_sizes.len() - 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let container_size = self.container_size();
|
|
||||||
self.sync_real_panel_sizes(cx);
|
|
||||||
|
|
||||||
let move_changed = size - old_sizes[ix];
|
|
||||||
if move_changed == px(0.) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let size_range = self.panel_size_range(ix);
|
|
||||||
let new_size = size.clamp(size_range.start, size_range.end);
|
|
||||||
let is_expand = move_changed > px(0.);
|
|
||||||
|
|
||||||
let main_ix = ix;
|
|
||||||
let mut new_sizes = old_sizes.clone();
|
|
||||||
|
|
||||||
if is_expand {
|
|
||||||
let mut changed = new_size - old_sizes[ix];
|
|
||||||
new_sizes[ix] = new_size;
|
|
||||||
|
|
||||||
while changed > px(0.) && ix < old_sizes.len() - 1 {
|
|
||||||
ix += 1;
|
|
||||||
let size_range = self.panel_size_range(ix);
|
|
||||||
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
|
||||||
let to_reduce = changed.min(available_size);
|
|
||||||
new_sizes[ix] -= to_reduce;
|
|
||||||
changed -= to_reduce;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut changed = new_size - size;
|
|
||||||
new_sizes[ix] = new_size;
|
|
||||||
|
|
||||||
while changed > px(0.) && ix > 0 {
|
|
||||||
ix -= 1;
|
|
||||||
let size_range = self.panel_size_range(ix);
|
|
||||||
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
|
||||||
let to_reduce = changed.min(available_size);
|
|
||||||
changed -= to_reduce;
|
|
||||||
new_sizes[ix] -= to_reduce;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_sizes[main_ix + 1] += old_sizes[main_ix] - size - changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
let total_size: Pixels = new_sizes.iter().map(|s| s.to_f64()).sum::<f64>().into();
|
|
||||||
|
|
||||||
// If total size exceeds container size, adjust the main panel
|
|
||||||
if total_size > container_size {
|
|
||||||
let overflow = total_size - container_size;
|
|
||||||
new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, _) in old_sizes.iter().enumerate() {
|
|
||||||
let size = new_sizes[i];
|
|
||||||
self.panels[i].size = Some(size);
|
|
||||||
}
|
|
||||||
self.sizes = new_sizes;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adjust panel sizes according to the container size.
|
|
||||||
///
|
|
||||||
/// When the container size changes, the panels should take up the same percentage as they did before.
|
|
||||||
fn adjust_to_container_size(&mut self, cx: &mut Context<Self>) {
|
|
||||||
if self.container_size().is_zero() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let container_size = self.container_size();
|
|
||||||
let total_size = px(self.sizes.iter().map(f32::from).sum::<f32>());
|
|
||||||
|
|
||||||
for i in 0..self.panels.len() {
|
|
||||||
let size = self.sizes[i];
|
|
||||||
let ratio = size / total_size;
|
|
||||||
let new_size = container_size * ratio;
|
|
||||||
|
|
||||||
self.sizes[i] = new_size;
|
|
||||||
self.panels[i].size = Some(new_size);
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<ResizablePanelEvent> for ResizableState {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub(crate) struct ResizablePanelState {
|
|
||||||
pub size: Option<Pixels>,
|
|
||||||
pub size_range: Range<Pixels>,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,408 +0,0 @@
|
|||||||
use std::ops::{Deref, Range};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
|
||||||
use gpui::{
|
|
||||||
Along, AnyElement, App, AppContext, Axis, Bounds, Context, Element, ElementId, Empty, Entity,
|
|
||||||
EventEmitter, InteractiveElement as _, IntoElement, IsZero as _, MouseMoveEvent, MouseUpEvent,
|
|
||||||
ParentElement, Pixels, Render, RenderOnce, Style, Styled, Window, div,
|
|
||||||
};
|
|
||||||
use theme::AxisExt;
|
|
||||||
|
|
||||||
use super::{ResizableState, resizable_panel, resize_handle};
|
|
||||||
use crate::resizable::PANEL_MIN_SIZE;
|
|
||||||
use crate::{ElementExt, h_flex, v_flex};
|
|
||||||
|
|
||||||
pub enum ResizablePanelEvent {
|
|
||||||
Resized,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct DragPanel;
|
|
||||||
impl Render for DragPanel {
|
|
||||||
fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
|
|
||||||
Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A group of resizable panels.
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct ResizablePanelGroup {
|
|
||||||
id: ElementId,
|
|
||||||
state: Option<Entity<ResizableState>>,
|
|
||||||
axis: Axis,
|
|
||||||
size: Option<Pixels>,
|
|
||||||
children: Vec<ResizablePanel>,
|
|
||||||
on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResizablePanelGroup {
|
|
||||||
/// Create a new resizable panel group.
|
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
|
||||||
Self {
|
|
||||||
id: id.into(),
|
|
||||||
axis: Axis::Horizontal,
|
|
||||||
children: vec![],
|
|
||||||
state: None,
|
|
||||||
size: None,
|
|
||||||
on_resize: Rc::new(|_, _, _| {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bind yourself to a resizable state entity.
|
|
||||||
///
|
|
||||||
/// If not provided, it will handle its own state internally.
|
|
||||||
pub fn with_state(mut self, state: &Entity<ResizableState>) -> Self {
|
|
||||||
self.state = Some(state.clone());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the axis of the resizable panel group, default is horizontal.
|
|
||||||
pub fn axis(mut self, axis: Axis) -> Self {
|
|
||||||
self.axis = axis;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a panel to the group.
|
|
||||||
///
|
|
||||||
/// - The `axis` will be set to the same axis as the group.
|
|
||||||
/// - The `initial_size` will be set to the average size of all panels if not provided.
|
|
||||||
/// - The `group` will be set to the group entity.
|
|
||||||
pub fn child(mut self, panel: impl Into<ResizablePanel>) -> Self {
|
|
||||||
self.children.push(panel.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add multiple panels to the group.
|
|
||||||
pub fn children<I>(mut self, panels: impl IntoIterator<Item = I>) -> Self
|
|
||||||
where
|
|
||||||
I: Into<ResizablePanel>,
|
|
||||||
{
|
|
||||||
self.children = panels.into_iter().map(|panel| panel.into()).collect();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set size of the resizable panel group
|
|
||||||
///
|
|
||||||
/// - When the axis is horizontal, the size is the height of the group.
|
|
||||||
/// - When the axis is vertical, the size is the width of the group.
|
|
||||||
pub fn size(mut self, size: Pixels) -> Self {
|
|
||||||
self.size = Some(size);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the callback to be called when the panels are resized.
|
|
||||||
///
|
|
||||||
/// ## Callback arguments
|
|
||||||
///
|
|
||||||
/// - Entity<ResizableState>: The state of the ResizablePanelGroup.
|
|
||||||
pub fn on_resize(
|
|
||||||
mut self,
|
|
||||||
on_resize: impl Fn(&Entity<ResizableState>, &mut Window, &mut App) + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.on_resize = Rc::new(on_resize);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for ResizablePanel
|
|
||||||
where
|
|
||||||
T: Into<AnyElement>,
|
|
||||||
{
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
resizable_panel().child(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ResizablePanelGroup> for ResizablePanel {
|
|
||||||
fn from(value: ResizablePanelGroup) -> Self {
|
|
||||||
resizable_panel().child(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
|
|
||||||
|
|
||||||
impl RenderOnce for ResizablePanelGroup {
|
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
let state = self.state.unwrap_or(
|
|
||||||
window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()),
|
|
||||||
);
|
|
||||||
let container = if self.axis.is_horizontal() {
|
|
||||||
h_flex()
|
|
||||||
} else {
|
|
||||||
v_flex()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sync panels to the state
|
|
||||||
let panels_count = self.children.len();
|
|
||||||
state.update(cx, |state, cx| {
|
|
||||||
state.sync_panels_count(self.axis, panels_count, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
container
|
|
||||||
.id(self.id)
|
|
||||||
.size_full()
|
|
||||||
.children(
|
|
||||||
self.children
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(ix, mut panel)| {
|
|
||||||
panel.panel_ix = ix;
|
|
||||||
panel.axis = self.axis;
|
|
||||||
panel.state = Some(state.clone());
|
|
||||||
panel
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.on_prepaint({
|
|
||||||
let state = state.clone();
|
|
||||||
move |bounds, _, cx| {
|
|
||||||
state.update(cx, |state, cx| {
|
|
||||||
let size_changed =
|
|
||||||
state.bounds.size.along(self.axis) != bounds.size.along(self.axis);
|
|
||||||
|
|
||||||
state.bounds = bounds;
|
|
||||||
|
|
||||||
if size_changed {
|
|
||||||
state.adjust_to_container_size(cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.child(ResizePanelGroupElement {
|
|
||||||
state: state.clone(),
|
|
||||||
axis: self.axis,
|
|
||||||
on_resize: self.on_resize.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A resizable panel inside a [`ResizablePanelGroup`].
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct ResizablePanel {
|
|
||||||
axis: Axis,
|
|
||||||
panel_ix: usize,
|
|
||||||
state: Option<Entity<ResizableState>>,
|
|
||||||
/// Initial size is the size that the panel has when it is created.
|
|
||||||
initial_size: Option<Pixels>,
|
|
||||||
/// size range limit of this panel.
|
|
||||||
size_range: Range<Pixels>,
|
|
||||||
children: Vec<AnyElement>,
|
|
||||||
visible: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResizablePanel {
|
|
||||||
/// Create a new resizable panel.
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
panel_ix: 0,
|
|
||||||
initial_size: None,
|
|
||||||
state: None,
|
|
||||||
size_range: (PANEL_MIN_SIZE..Pixels::MAX),
|
|
||||||
axis: Axis::Horizontal,
|
|
||||||
children: vec![],
|
|
||||||
visible: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the visibility of the panel, default is true.
|
|
||||||
pub fn visible(mut self, visible: bool) -> Self {
|
|
||||||
self.visible = visible;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the initial size of the panel.
|
|
||||||
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
|
||||||
self.initial_size = Some(size.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the size range to limit panel resize.
|
|
||||||
///
|
|
||||||
/// Default is [`PANEL_MIN_SIZE`] to [`Pixels::MAX`].
|
|
||||||
pub fn size_range(mut self, range: impl Into<Range<Pixels>>) -> Self {
|
|
||||||
self.size_range = range.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParentElement for ResizablePanel {
|
|
||||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
|
||||||
self.children.extend(elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for ResizablePanel {
|
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
if !self.visible {
|
|
||||||
return div().id(("resizable-panel", self.panel_ix));
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = self
|
|
||||||
.state
|
|
||||||
.expect("BUG: The `state` in ResizablePanel should be present.");
|
|
||||||
|
|
||||||
let panel_state = state
|
|
||||||
.read(cx)
|
|
||||||
.panels
|
|
||||||
.get(self.panel_ix)
|
|
||||||
.expect("BUG: The `index` of ResizablePanel should be one of in `state`.");
|
|
||||||
|
|
||||||
let size_range = self.size_range.clone();
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id(("resizable-panel", self.panel_ix))
|
|
||||||
.flex()
|
|
||||||
.flex_grow_1()
|
|
||||||
.size_full()
|
|
||||||
.relative()
|
|
||||||
.when(self.axis.is_vertical(), |this| {
|
|
||||||
this.min_h(size_range.start).max_h(size_range.end)
|
|
||||||
})
|
|
||||||
.when(self.axis.is_horizontal(), |this| {
|
|
||||||
this.min_w(size_range.start).max_w(size_range.end)
|
|
||||||
})
|
|
||||||
// 1. initial_size is None, to use auto size.
|
|
||||||
// 2. initial_size is Some and size is none, to use the initial size of the panel for first time render.
|
|
||||||
// 3. initial_size is Some and size is Some, use `size`.
|
|
||||||
.when(self.initial_size.is_none(), |this| this.flex_shrink_1())
|
|
||||||
.when_some(self.initial_size, |this, initial_size| {
|
|
||||||
// The `self.size` is None, that mean the initial size for the panel,
|
|
||||||
// so we need set `flex_shrink_0` To let it keep the initial size.
|
|
||||||
this.when(
|
|
||||||
panel_state.size.is_none() && !initial_size.is_zero(),
|
|
||||||
|this| this.flex_none(),
|
|
||||||
)
|
|
||||||
.flex_basis(initial_size)
|
|
||||||
})
|
|
||||||
.map(|this| match panel_state.size {
|
|
||||||
Some(size) => this.flex_basis(size.min(size_range.end).max(size_range.start)),
|
|
||||||
None => this,
|
|
||||||
})
|
|
||||||
.on_prepaint({
|
|
||||||
let state = state.clone();
|
|
||||||
move |bounds, _, cx| {
|
|
||||||
state.update(cx, |state, cx| {
|
|
||||||
state.update_panel_size(self.panel_ix, bounds, self.size_range, cx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.children(self.children)
|
|
||||||
.when(self.panel_ix > 0, |this| {
|
|
||||||
let ix = self.panel_ix - 1;
|
|
||||||
this.child(resize_handle(("resizable-handle", ix), self.axis).on_drag(
|
|
||||||
DragPanel,
|
|
||||||
move |drag_panel, _, _, cx| {
|
|
||||||
cx.stop_propagation();
|
|
||||||
// Set current resizing panel ix
|
|
||||||
state.update(cx, |state, _| {
|
|
||||||
state.resizing_panel_ix = Some(ix);
|
|
||||||
});
|
|
||||||
cx.new(|_| drag_panel.deref().clone())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
struct ResizePanelGroupElement {
|
|
||||||
state: Entity<ResizableState>,
|
|
||||||
on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
|
|
||||||
axis: Axis,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoElement for ResizePanelGroupElement {
|
|
||||||
type Element = Self;
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for ResizePanelGroupElement {
|
|
||||||
type PrepaintState = ();
|
|
||||||
type RequestLayoutState = ();
|
|
||||||
|
|
||||||
fn id(&self) -> Option<gpui::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
|
||||||
(window.request_layout(Style::default(), None, cx), ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepaint(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
_: Bounds<Pixels>,
|
|
||||||
_: &mut Self::RequestLayoutState,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut App,
|
|
||||||
) -> Self::PrepaintState {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
_: Bounds<Pixels>,
|
|
||||||
_: &mut Self::RequestLayoutState,
|
|
||||||
_: &mut Self::PrepaintState,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
window.on_mouse_event({
|
|
||||||
let state = self.state.clone();
|
|
||||||
let axis = self.axis;
|
|
||||||
let current_ix = state.read(cx).resizing_panel_ix;
|
|
||||||
move |e: &MouseMoveEvent, phase, window, cx| {
|
|
||||||
if !phase.bubble() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(ix) = current_ix else { return };
|
|
||||||
|
|
||||||
state.update(cx, |state, cx| {
|
|
||||||
let panel = state.panels.get(ix).expect("BUG: invalid panel index");
|
|
||||||
|
|
||||||
match axis {
|
|
||||||
Axis::Horizontal => {
|
|
||||||
state.resize_panel(ix, e.position.x - panel.bounds.left(), window, cx)
|
|
||||||
}
|
|
||||||
Axis::Vertical => {
|
|
||||||
state.resize_panel(ix, e.position.y - panel.bounds.top(), window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// When any mouse up, stop dragging
|
|
||||||
window.on_mouse_event({
|
|
||||||
let state = self.state.clone();
|
|
||||||
let current_ix = state.read(cx).resizing_panel_ix;
|
|
||||||
let on_resize = self.on_resize.clone();
|
|
||||||
move |_: &MouseUpEvent, phase, window, cx| {
|
|
||||||
if current_ix.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if phase.bubble() {
|
|
||||||
state.update(cx, |state, cx| state.done_resizing(cx));
|
|
||||||
on_resize(&state, window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
use std::cell::Cell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder as _;
|
|
||||||
use gpui::{
|
|
||||||
AnyElement, App, Axis, Element, ElementId, Entity, GlobalElementId, InteractiveElement,
|
|
||||||
IntoElement, MouseDownEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render,
|
|
||||||
StatefulInteractiveElement, Styled as _, Window, div, px,
|
|
||||||
};
|
|
||||||
use theme::{ActiveTheme, AxisExt};
|
|
||||||
|
|
||||||
use crate::dock::DockPlacement;
|
|
||||||
|
|
||||||
pub(crate) const HANDLE_PADDING: Pixels = px(4.);
|
|
||||||
pub(crate) const HANDLE_SIZE: Pixels = px(1.);
|
|
||||||
|
|
||||||
/// Create a resize handle for a resizable panel.
|
|
||||||
pub(crate) fn resize_handle<T: 'static, E: 'static + Render>(
|
|
||||||
id: impl Into<ElementId>,
|
|
||||||
axis: Axis,
|
|
||||||
) -> ResizeHandle<T, E> {
|
|
||||||
ResizeHandle::new(id, axis)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub(crate) struct ResizeHandle<T: 'static, E: 'static + Render> {
|
|
||||||
id: ElementId,
|
|
||||||
axis: Axis,
|
|
||||||
drag_value: Option<Rc<T>>,
|
|
||||||
placement: Option<DockPlacement>,
|
|
||||||
on_drag: Option<Rc<dyn Fn(&Point<Pixels>, &mut Window, &mut App) -> Entity<E>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static, E: 'static + Render> ResizeHandle<T, E> {
|
|
||||||
fn new(id: impl Into<ElementId>, axis: Axis) -> Self {
|
|
||||||
let id = id.into();
|
|
||||||
Self {
|
|
||||||
id: id.clone(),
|
|
||||||
on_drag: None,
|
|
||||||
drag_value: None,
|
|
||||||
placement: None,
|
|
||||||
axis,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn on_drag(
|
|
||||||
mut self,
|
|
||||||
value: T,
|
|
||||||
f: impl Fn(Rc<T>, &Point<Pixels>, &mut Window, &mut App) -> Entity<E> + 'static,
|
|
||||||
) -> Self {
|
|
||||||
let value = Rc::new(value);
|
|
||||||
self.drag_value = Some(value.clone());
|
|
||||||
self.on_drag = Some(Rc::new(move |p, window, cx| {
|
|
||||||
f(value.clone(), p, window, cx)
|
|
||||||
}));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn placement(mut self, placement: DockPlacement) -> Self {
|
|
||||||
self.placement = Some(placement);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
struct ResizeHandleState {
|
|
||||||
active: Cell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResizeHandleState {
|
|
||||||
fn set_active(&self, active: bool) {
|
|
||||||
self.active.set(active);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_active(&self) -> bool {
|
|
||||||
self.active.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static, E: 'static + Render> IntoElement for ResizeHandle<T, E> {
|
|
||||||
type Element = ResizeHandle<T, E>;
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static, E: 'static + Render> Element for ResizeHandle<T, E> {
|
|
||||||
type PrepaintState = ();
|
|
||||||
type RequestLayoutState = AnyElement;
|
|
||||||
|
|
||||||
fn id(&self) -> Option<ElementId> {
|
|
||||||
Some(self.id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
id: Option<&GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
|
||||||
let neg_offset = -HANDLE_PADDING;
|
|
||||||
let axis = self.axis;
|
|
||||||
|
|
||||||
window.with_element_state(id.unwrap(), |state, window| {
|
|
||||||
let state = state.unwrap_or(ResizeHandleState::default());
|
|
||||||
|
|
||||||
let bg_color = if state.is_active() {
|
|
||||||
cx.theme().border_selected
|
|
||||||
} else {
|
|
||||||
cx.theme().border
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ele = div()
|
|
||||||
.id(self.id.clone())
|
|
||||||
.occlude()
|
|
||||||
.absolute()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.group("handle")
|
|
||||||
.when_some(self.on_drag.clone(), |this, on_drag| {
|
|
||||||
this.on_drag(
|
|
||||||
self.drag_value.clone().unwrap(),
|
|
||||||
move |_, position, window, cx| on_drag(&position, window, cx),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(|this| match self.placement {
|
|
||||||
Some(DockPlacement::Left) => {
|
|
||||||
// Special for Left Dock
|
|
||||||
// FIXME: Improve this to let the scroll bar have px(HANDLE_PADDING)
|
|
||||||
this.cursor_col_resize()
|
|
||||||
.top_0()
|
|
||||||
.right(px(1.))
|
|
||||||
.h_full()
|
|
||||||
.w(HANDLE_SIZE)
|
|
||||||
.pl(HANDLE_PADDING)
|
|
||||||
}
|
|
||||||
_ => this
|
|
||||||
.when(axis.is_horizontal(), |this| {
|
|
||||||
this.cursor_col_resize()
|
|
||||||
.top_0()
|
|
||||||
.left(neg_offset)
|
|
||||||
.h_full()
|
|
||||||
.w(HANDLE_SIZE)
|
|
||||||
.px(HANDLE_PADDING)
|
|
||||||
})
|
|
||||||
.when(axis.is_vertical(), |this| {
|
|
||||||
this.cursor_row_resize()
|
|
||||||
.top(neg_offset)
|
|
||||||
.left_0()
|
|
||||||
.w_full()
|
|
||||||
.h(HANDLE_SIZE)
|
|
||||||
.py(HANDLE_PADDING)
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.group_hover("handle", |this| this.bg(bg_color))
|
|
||||||
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
|
|
||||||
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
|
|
||||||
)
|
|
||||||
.into_any_element();
|
|
||||||
|
|
||||||
let layout_id = ele.request_layout(window, cx);
|
|
||||||
|
|
||||||
((layout_id, ele), state)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepaint(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
_: gpui::Bounds<Pixels>,
|
|
||||||
request_layout: &mut Self::RequestLayoutState,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Self::PrepaintState {
|
|
||||||
request_layout.prepaint(window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
id: Option<&GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
bounds: gpui::Bounds<Pixels>,
|
|
||||||
request_layout: &mut Self::RequestLayoutState,
|
|
||||||
_: &mut Self::PrepaintState,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
request_layout.paint(window, cx);
|
|
||||||
|
|
||||||
window.with_element_state(id.unwrap(), |state: Option<ResizeHandleState>, window| {
|
|
||||||
let state = state.unwrap_or_default();
|
|
||||||
|
|
||||||
window.on_mouse_event({
|
|
||||||
let state = state.clone();
|
|
||||||
move |ev: &MouseDownEvent, phase, window, _| {
|
|
||||||
if bounds.contains(&ev.position) && phase.bubble() {
|
|
||||||
state.set_active(true);
|
|
||||||
window.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.on_mouse_event({
|
|
||||||
let state = state.clone();
|
|
||||||
move |_: &MouseUpEvent, _, window, _| {
|
|
||||||
if state.is_active() {
|
|
||||||
state.set_active(false);
|
|
||||||
window.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
((), state)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user