chore: Upgrade to GPUI3 (#6)
* wip: gpui3 * wip: gpui3 * chore: fix clippy
This commit is contained in:
@@ -5,9 +5,9 @@ use crate::{
|
||||
Disableable, Icon, Selectable, Sizable, Size, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Corners, Div, Edges,
|
||||
ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
div, prelude::FluentBuilder as _, px, relative, AnyElement, App, ClickEvent, Corners, Div,
|
||||
Edges, ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, Window,
|
||||
};
|
||||
|
||||
pub enum ButtonRounded {
|
||||
@@ -64,7 +64,7 @@ pub trait ButtonVariants: Sized {
|
||||
}
|
||||
|
||||
impl ButtonCustomVariant {
|
||||
pub fn new(cx: &WindowContext) -> Self {
|
||||
pub fn new(_window: &Window, cx: &App) -> Self {
|
||||
Self {
|
||||
color: cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
foreground: cx.theme().accent.step(cx, ColorScaleStep::ONE),
|
||||
@@ -136,7 +136,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>;
|
||||
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>;
|
||||
|
||||
/// A Button element.
|
||||
#[derive(IntoElement)]
|
||||
@@ -253,7 +253,10 @@ impl Button {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
@@ -320,9 +323,9 @@ impl InteractiveElement for Button {
|
||||
}
|
||||
|
||||
impl RenderOnce for Button {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let style: ButtonVariant = self.variant;
|
||||
let normal_style = style.normal(cx);
|
||||
let normal_style = style.normal(window, cx);
|
||||
let icon_size = match self.size {
|
||||
Size::Size(v) => Size::Size(v * 0.75),
|
||||
_ => self.size,
|
||||
@@ -384,7 +387,7 @@ impl RenderOnce for Button {
|
||||
.when(self.border_edges.bottom, |this| this.border_b_1())
|
||||
.text_color(normal_style.fg)
|
||||
.when(self.selected, |this| {
|
||||
let selected_style = style.selected(cx);
|
||||
let selected_style = style.selected(window, cx);
|
||||
this.bg(selected_style.bg)
|
||||
.border_color(selected_style.border)
|
||||
.text_color(selected_style.fg)
|
||||
@@ -394,11 +397,11 @@ impl RenderOnce for Button {
|
||||
.bg(normal_style.bg)
|
||||
.when(normal_style.underline, |this| this.text_decoration_1())
|
||||
.hover(|this| {
|
||||
let hover_style = style.hovered(cx);
|
||||
let hover_style = style.hovered(window, cx);
|
||||
this.bg(hover_style.bg).border_color(hover_style.border)
|
||||
})
|
||||
.active(|this| {
|
||||
let active_style = style.active(cx);
|
||||
let active_style = style.active(window, cx);
|
||||
this.bg(active_style.bg)
|
||||
.border_color(active_style.border)
|
||||
.text_color(active_style.fg)
|
||||
@@ -408,19 +411,19 @@ impl RenderOnce for Button {
|
||||
self.on_click.filter(|_| !self.disabled && !self.loading),
|
||||
|this, on_click| {
|
||||
let stop_propagation = self.stop_propagation;
|
||||
this.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||
cx.prevent_default();
|
||||
this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.prevent_default();
|
||||
if stop_propagation {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
})
|
||||
.on_click(move |event, cx| {
|
||||
(on_click)(event, cx);
|
||||
.on_click(move |event, window, cx| {
|
||||
(on_click)(event, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
.when(self.disabled, |this| {
|
||||
let disabled_style = style.disabled(cx);
|
||||
let disabled_style = style.disabled(window, cx);
|
||||
this.cursor_not_allowed()
|
||||
.bg(disabled_style.bg)
|
||||
.text_color(disabled_style.fg)
|
||||
@@ -464,7 +467,7 @@ impl RenderOnce for Button {
|
||||
})
|
||||
.when(self.loading, |this| this.bg(normal_style.bg.opacity(0.8)))
|
||||
.when_some(self.tooltip.clone(), |this, tooltip| {
|
||||
this.tooltip(move |cx| Tooltip::new(tooltip.clone(), cx).into())
|
||||
this.tooltip(move |window, cx| Tooltip::new(tooltip.clone(), window, cx).into())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -478,7 +481,7 @@ struct ButtonVariantStyle {
|
||||
}
|
||||
|
||||
impl ButtonVariant {
|
||||
fn bg_color(&self, cx: &WindowContext) -> Hsla {
|
||||
fn bg_color(&self, _window: &Window, cx: &App) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
ButtonVariant::Custom(colors) => colors.color,
|
||||
@@ -486,7 +489,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn text_color(&self, cx: &WindowContext) -> Hsla {
|
||||
fn text_color(&self, _window: &Window, cx: &App) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Primary => match cx.theme().accent.name().to_string().as_str() {
|
||||
"Sky" => cx.theme().base.darken(cx),
|
||||
@@ -503,7 +506,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn border_color(&self, cx: &WindowContext) -> Hsla {
|
||||
fn border_color(&self, _window: &Window, cx: &App) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
|
||||
@@ -513,11 +516,11 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn underline(&self, _: &WindowContext) -> bool {
|
||||
fn underline(&self, _window: &Window, _cx: &App) -> bool {
|
||||
matches!(self, ButtonVariant::Link)
|
||||
}
|
||||
|
||||
fn shadow(&self, _: &WindowContext) -> bool {
|
||||
fn shadow(&self, _window: &Window, _cx: &App) -> bool {
|
||||
match self {
|
||||
ButtonVariant::Primary => true,
|
||||
ButtonVariant::Custom(c) => c.shadow,
|
||||
@@ -525,12 +528,12 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn normal(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
let bg = self.bg_color(cx);
|
||||
let border = self.border_color(cx);
|
||||
let fg = self.text_color(cx);
|
||||
let underline = self.underline(cx);
|
||||
let shadow = self.shadow(cx);
|
||||
fn normal(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
|
||||
let bg = self.bg_color(window, cx);
|
||||
let border = self.border_color(window, cx);
|
||||
let fg = self.text_color(window, cx);
|
||||
let underline = self.underline(window, cx);
|
||||
let shadow = self.shadow(window, cx);
|
||||
|
||||
ButtonVariantStyle {
|
||||
bg,
|
||||
@@ -541,7 +544,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
fn hovered(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||
@@ -549,14 +552,14 @@ impl ButtonVariant {
|
||||
ButtonVariant::Text => cx.theme().transparent,
|
||||
ButtonVariant::Custom(colors) => colors.hover,
|
||||
};
|
||||
let border = self.border_color(cx);
|
||||
let border = self.border_color(window, cx);
|
||||
let fg = match self {
|
||||
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
|
||||
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
_ => self.text_color(cx),
|
||||
_ => self.text_color(window, cx),
|
||||
};
|
||||
let underline = self.underline(cx);
|
||||
let shadow = self.shadow(cx);
|
||||
let underline = self.underline(window, cx);
|
||||
let shadow = self.shadow(window, cx);
|
||||
|
||||
ButtonVariantStyle {
|
||||
bg,
|
||||
@@ -567,7 +570,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn active(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
fn active(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
|
||||
@@ -579,12 +582,12 @@ impl ButtonVariant {
|
||||
let fg = match self {
|
||||
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
ButtonVariant::Text => cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
_ => self.text_color(cx),
|
||||
_ => self.text_color(window, cx),
|
||||
};
|
||||
|
||||
let border = self.border_color(cx);
|
||||
let underline = self.underline(cx);
|
||||
let shadow = self.shadow(cx);
|
||||
let border = self.border_color(window, cx);
|
||||
let underline = self.underline(window, cx);
|
||||
let shadow = self.shadow(window, cx);
|
||||
|
||||
ButtonVariantStyle {
|
||||
bg,
|
||||
@@ -595,7 +598,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn selected(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
fn selected(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
|
||||
@@ -607,12 +610,12 @@ impl ButtonVariant {
|
||||
let fg = match self {
|
||||
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
ButtonVariant::Text => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
_ => self.text_color(cx),
|
||||
_ => self.text_color(window, cx),
|
||||
};
|
||||
|
||||
let border = self.border_color(cx);
|
||||
let underline = self.underline(cx);
|
||||
let shadow = self.shadow(cx);
|
||||
let border = self.border_color(window, cx);
|
||||
let underline = self.underline(window, cx);
|
||||
let shadow = self.shadow(window, cx);
|
||||
|
||||
ButtonVariantStyle {
|
||||
bg,
|
||||
@@ -623,7 +626,7 @@ impl ButtonVariant {
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
fn disabled(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
|
||||
cx.theme().transparent
|
||||
@@ -633,7 +636,7 @@ impl ButtonVariant {
|
||||
|
||||
let fg = cx.theme().base.step(cx, ColorScaleStep::ELEVEN);
|
||||
let border = bg;
|
||||
let underline = self.underline(cx);
|
||||
let underline = self.underline(window, cx);
|
||||
let shadow = false;
|
||||
|
||||
ButtonVariantStyle {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, Corners, Div, Edges, ElementId, InteractiveElement,
|
||||
IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
div, prelude::FluentBuilder as _, App, Corners, Div, Edges, ElementId, InteractiveElement,
|
||||
IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, Styled, Window,
|
||||
};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
Disableable, Sizable, Size,
|
||||
};
|
||||
|
||||
type OnClick = Option<Box<dyn Fn(&Vec<usize>, &mut WindowContext) + 'static>>;
|
||||
type OnClick = Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>;
|
||||
|
||||
/// A ButtonGroup element, to wrap multiple buttons in a group.
|
||||
#[derive(IntoElement)]
|
||||
@@ -72,7 +72,10 @@ impl ButtonGroup {
|
||||
/// Sets the on_click handler for the ButtonGroup.
|
||||
///
|
||||
/// The handler first argument is a vector of the selected button indices.
|
||||
pub fn on_click(mut self, handler: impl Fn(&Vec<usize>, &mut WindowContext) + 'static) -> Self {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&Vec<usize>, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
@@ -99,7 +102,7 @@ impl ButtonVariants for ButtonGroup {
|
||||
}
|
||||
|
||||
impl RenderOnce for ButtonGroup {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let children_len = self.children.len();
|
||||
let mut selected_ixs: Vec<usize> = Vec::new();
|
||||
let state = Rc::new(Cell::new(None));
|
||||
@@ -167,7 +170,7 @@ impl RenderOnce for ButtonGroup {
|
||||
.stop_propagation(false)
|
||||
.when_some(self.size, |this, size| this.with_size(size))
|
||||
.when_some(self.variant, |this, variant| this.with_variant(variant))
|
||||
.on_click(move |_, _| {
|
||||
.on_click(move |_, _, _| {
|
||||
state.set(Some(child_index));
|
||||
})
|
||||
}),
|
||||
@@ -175,7 +178,7 @@ impl RenderOnce for ButtonGroup {
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
move |this, on_click| {
|
||||
this.on_click(move |_, cx| {
|
||||
this.on_click(move |_, window, cx| {
|
||||
let mut selected_ixs = selected_ixs.clone();
|
||||
if let Some(ix) = state.get() {
|
||||
if self.multiple {
|
||||
@@ -190,7 +193,7 @@ impl RenderOnce for ButtonGroup {
|
||||
}
|
||||
}
|
||||
|
||||
on_click(&selected_ixs, cx);
|
||||
on_click(&selected_ixs, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -4,12 +4,12 @@ use crate::{
|
||||
v_flex, Disableable, IconName, Selectable,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, relative, svg, ElementId, InteractiveElement, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _,
|
||||
WindowContext,
|
||||
div, prelude::FluentBuilder as _, relative, svg, App, ElementId, InteractiveElement,
|
||||
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _,
|
||||
Styled as _, Window,
|
||||
};
|
||||
|
||||
type OnClick = Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>;
|
||||
type OnClick = Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>;
|
||||
|
||||
/// A Checkbox element.
|
||||
#[derive(IntoElement)]
|
||||
@@ -42,7 +42,7 @@ impl Checkbox {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&bool, &mut WindowContext) + 'static) -> Self {
|
||||
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
@@ -66,7 +66,7 @@ impl Selectable for Checkbox {
|
||||
}
|
||||
|
||||
impl RenderOnce for Checkbox {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let (color, icon_color) = if self.disabled {
|
||||
(
|
||||
cx.theme().base.step(cx, ColorScaleStep::THREE),
|
||||
@@ -131,9 +131,9 @@ impl RenderOnce for Checkbox {
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
|this, on_click| {
|
||||
this.on_click(move |_, cx| {
|
||||
this.on_click(move |_, window, cx| {
|
||||
let checked = !self.checked;
|
||||
on_click(&checked, cx);
|
||||
on_click(&checked, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
use gpui::{
|
||||
prelude::FluentBuilder, AnyElement, ClipboardItem, Element, ElementId, GlobalElementId,
|
||||
IntoElement, LayoutId, ParentElement, SharedString, Styled, WindowContext,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
button::{Button, ButtonVariants as _},
|
||||
h_flex, IconName, Sizable as _,
|
||||
};
|
||||
|
||||
type ContentBuilder = Option<Box<dyn Fn(&mut WindowContext) -> AnyElement>>;
|
||||
type CopiedCallback = Option<Rc<dyn Fn(SharedString, &mut WindowContext)>>;
|
||||
use gpui::{
|
||||
prelude::FluentBuilder, AnyElement, App, ClipboardItem, Element, ElementId, GlobalElementId,
|
||||
IntoElement, LayoutId, ParentElement, SharedString, Styled, Window,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
pub struct Clipboard {
|
||||
id: ElementId,
|
||||
value: SharedString,
|
||||
content_builder: ContentBuilder,
|
||||
copied_callback: CopiedCallback,
|
||||
content_builder: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
|
||||
copied_callback: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,
|
||||
}
|
||||
|
||||
impl Clipboard {
|
||||
@@ -35,18 +30,20 @@ impl Clipboard {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content<E, F>(mut self, element_builder: F) -> Self
|
||||
pub fn content<E, F>(mut self, builder: F) -> Self
|
||||
where
|
||||
E: IntoElement,
|
||||
F: Fn(&mut WindowContext) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut App) -> E + 'static,
|
||||
{
|
||||
self.content_builder = Some(Box::new(move |cx| element_builder(cx).into_any_element()));
|
||||
self.content_builder = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_copied<F>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: Fn(SharedString, &mut WindowContext) + 'static,
|
||||
F: Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||
{
|
||||
self.copied_callback = Some(Rc::new(handler));
|
||||
self
|
||||
@@ -78,15 +75,16 @@ impl Element for Clipboard {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
cx.with_element_state::<ClipboardState, _>(global_id.unwrap(), |state, cx| {
|
||||
window.with_element_state::<ClipboardState, _>(global_id.unwrap(), |state, window| {
|
||||
let state = state.unwrap_or_default();
|
||||
|
||||
let content_element = self
|
||||
.content_builder
|
||||
.as_ref()
|
||||
.map(|builder| builder(cx).into_any_element());
|
||||
.map(|builder| builder(window, cx).into_any_element());
|
||||
let value = self.value.clone();
|
||||
let clipboard_id = self.id.clone();
|
||||
let copied_callback = self.copied_callback.as_ref().map(|c| c.clone());
|
||||
@@ -107,7 +105,7 @@ impl Element for Clipboard {
|
||||
.ghost()
|
||||
.xsmall()
|
||||
.when(!copide_value, |this| {
|
||||
this.on_click(move |_, cx| {
|
||||
this.on_click(move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
|
||||
*copied.borrow_mut() = true;
|
||||
@@ -121,14 +119,14 @@ impl Element for Clipboard {
|
||||
.detach();
|
||||
|
||||
if let Some(callback) = &copied_callback {
|
||||
callback(value.clone(), cx);
|
||||
callback(value.clone(), window, cx);
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
((element.request_layout(cx), element), state)
|
||||
((element.request_layout(window, cx), element), state)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -137,9 +135,10 @@ impl Element for Clipboard {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<gpui::Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
element.prepaint(cx);
|
||||
element.prepaint(window, cx);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
@@ -148,8 +147,9 @@ impl Element for Clipboard {
|
||||
_: gpui::Bounds<gpui::Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
element.paint(cx)
|
||||
element.paint(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
use crate::popup_menu::PopupMenu;
|
||||
use gpui::{
|
||||
anchored, deferred, div, prelude::FluentBuilder, px, relative, AnyElement, Corner,
|
||||
DismissEvent, DispatchPhase, Element, ElementId, Focusable, GlobalElementId,
|
||||
InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
|
||||
Position, Stateful, Style, View, ViewContext, WindowContext,
|
||||
anchored, deferred, div, prelude::FluentBuilder, px, relative, AnyElement, App, Context,
|
||||
Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, Focusable, FocusableWrapper,
|
||||
GlobalElementId, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement,
|
||||
Pixels, Point, Position, Size, Stateful, Style, Window,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub trait ContextMenuExt: ParentElement + Sized {
|
||||
fn context_menu(
|
||||
self,
|
||||
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Self {
|
||||
self.child(ContextMenu::new("context-menu").menu(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {}
|
||||
impl<E> ContextMenuExt for Focusable<E> where E: ParentElement {}
|
||||
|
||||
type Menu<M> = Option<Box<dyn Fn(PopupMenu, &mut ViewContext<M>) -> PopupMenu + 'static>>;
|
||||
impl<E> ContextMenuExt for FocusableWrapper<E> where E: ParentElement {}
|
||||
|
||||
/// A context menu that can be shown on right-click.
|
||||
pub struct ContextMenu {
|
||||
id: ElementId,
|
||||
menu: Menu<PopupMenu>,
|
||||
menu:
|
||||
Option<Box<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static>>,
|
||||
anchor: Corner,
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ impl ContextMenu {
|
||||
#[must_use]
|
||||
pub fn menu<F>(mut self, builder: F) -> Self
|
||||
where
|
||||
F: Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
F: Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
{
|
||||
self.menu = Some(Box::new(builder));
|
||||
self
|
||||
@@ -49,14 +48,18 @@ impl ContextMenu {
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
id: &GlobalElementId,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut Self, &mut ContextMenuState, &mut WindowContext) -> R,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
f: impl FnOnce(&mut Self, &mut ContextMenuState, &mut Window, &mut App) -> R,
|
||||
) -> R {
|
||||
cx.with_optional_element_state::<ContextMenuState, _>(Some(id), |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, cx);
|
||||
(result, Some(element_state))
|
||||
})
|
||||
window.with_optional_element_state::<ContextMenuState, _>(
|
||||
Some(id),
|
||||
|element_state, window| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, window, cx);
|
||||
(result, Some(element_state))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +72,7 @@ impl IntoElement for ContextMenu {
|
||||
}
|
||||
|
||||
pub struct ContextMenuState {
|
||||
menu_view: Rc<RefCell<Option<View<PopupMenu>>>>,
|
||||
menu_view: Rc<RefCell<Option<Entity<PopupMenu>>>>,
|
||||
menu_element: Option<AnyElement>,
|
||||
open: Rc<RefCell<bool>>,
|
||||
position: Rc<RefCell<Point<Pixels>>>,
|
||||
@@ -97,75 +100,79 @@ impl Element for ContextMenu {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
// Set the layout style relative to the table view to get same size.
|
||||
let anchor = self.anchor;
|
||||
let style = Style {
|
||||
position: Position::Absolute,
|
||||
flex_grow: 1.0,
|
||||
flex_shrink: 1.0,
|
||||
size: gpui::Size {
|
||||
size: Size {
|
||||
width: relative(1.).into(),
|
||||
height: relative(1.).into(),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let anchor = self.anchor;
|
||||
self.with_element_state(
|
||||
id.unwrap(),
|
||||
window,
|
||||
cx,
|
||||
|_, state: &mut ContextMenuState, window, cx| {
|
||||
let position = state.position.clone();
|
||||
let position = position.borrow();
|
||||
let open = state.open.clone();
|
||||
let menu_view = state.menu_view.borrow().clone();
|
||||
|
||||
self.with_element_state(id.unwrap(), cx, |_, state: &mut ContextMenuState, cx| {
|
||||
let position = state.position.clone();
|
||||
let position = position.borrow();
|
||||
let open = state.open.clone();
|
||||
let menu_view = state.menu_view.borrow().clone();
|
||||
let (menu_element, menu_layout_id) = if *open.borrow() {
|
||||
let has_menu_item = menu_view
|
||||
.as_ref()
|
||||
.map(|menu| !menu.read(cx).is_empty())
|
||||
.unwrap_or(false);
|
||||
|
||||
let (menu_element, menu_layout_id) = if *open.borrow() {
|
||||
let has_menu_item = menu_view
|
||||
.as_ref()
|
||||
.map(|menu| !menu.read(cx).is_empty())
|
||||
.unwrap_or(false);
|
||||
if has_menu_item {
|
||||
let mut menu_element = deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(anchor)
|
||||
.when_some(menu_view, |this, menu| {
|
||||
// Focus the menu, so that can be handle the action.
|
||||
menu.focus_handle(cx).focus(window);
|
||||
|
||||
if has_menu_item {
|
||||
let mut menu_element = deferred(
|
||||
anchored()
|
||||
.position(*position)
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(anchor)
|
||||
.when_some(menu_view, |this, menu| {
|
||||
// Focus the menu, so that can be handle the action.
|
||||
menu.focus_handle(cx).focus(cx);
|
||||
this.child(div().occlude().child(menu.clone()))
|
||||
}),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any();
|
||||
|
||||
this.child(div().occlude().child(menu.clone()))
|
||||
}),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any();
|
||||
|
||||
let menu_layout_id = menu_element.request_layout(cx);
|
||||
(Some(menu_element), Some(menu_layout_id))
|
||||
let menu_layout_id = menu_element.request_layout(window, cx);
|
||||
(Some(menu_element), Some(menu_layout_id))
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let mut layout_ids = vec![];
|
||||
if let Some(menu_layout_id) = menu_layout_id {
|
||||
layout_ids.push(menu_layout_id);
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let mut layout_ids = vec![];
|
||||
if let Some(menu_layout_id) = menu_layout_id {
|
||||
layout_ids.push(menu_layout_id);
|
||||
}
|
||||
let layout_id = window.request_layout(style, layout_ids, cx);
|
||||
|
||||
let layout_id = cx.request_layout(style, layout_ids);
|
||||
(
|
||||
layout_id,
|
||||
ContextMenuState {
|
||||
menu_element,
|
||||
|
||||
(
|
||||
layout_id,
|
||||
ContextMenuState {
|
||||
menu_element,
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -173,10 +180,11 @@ impl Element for ContextMenu {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
if let Some(menu_element) = &mut request_layout.menu_element {
|
||||
menu_element.prepaint(cx);
|
||||
menu_element.prepaint(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,10 +194,11 @@ impl Element for ContextMenu {
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
if let Some(menu_element) = &mut request_layout.menu_element {
|
||||
menu_element.paint(cx);
|
||||
menu_element.paint(window, cx);
|
||||
}
|
||||
|
||||
let Some(builder) = self.menu.take() else {
|
||||
@@ -198,14 +207,15 @@ impl Element for ContextMenu {
|
||||
|
||||
self.with_element_state(
|
||||
id.unwrap(),
|
||||
window,
|
||||
cx,
|
||||
|_view, state: &mut ContextMenuState, cx| {
|
||||
|_view, state: &mut ContextMenuState, window, _| {
|
||||
let position = state.position.clone();
|
||||
let open = state.open.clone();
|
||||
let menu_view = state.menu_view.clone();
|
||||
|
||||
// When right mouse click, to build content menu, and show it at the mouse position.
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Right
|
||||
&& bounds.contains(&event.position)
|
||||
@@ -213,19 +223,22 @@ impl Element for ContextMenu {
|
||||
*position.borrow_mut() = event.position;
|
||||
*open.borrow_mut() = true;
|
||||
|
||||
let menu =
|
||||
PopupMenu::build(cx, |menu, cx| (builder)(menu, cx)).into_element();
|
||||
let menu = PopupMenu::build(window, cx, |menu, window, cx| {
|
||||
(builder)(menu, window, cx)
|
||||
})
|
||||
.into_element();
|
||||
|
||||
let open = open.clone();
|
||||
cx.subscribe(&menu, move |_, _: &DismissEvent, cx| {
|
||||
*open.borrow_mut() = false;
|
||||
cx.refresh();
|
||||
})
|
||||
.detach();
|
||||
window
|
||||
.subscribe(&menu, cx, move |_, _: &DismissEvent, window, _| {
|
||||
*open.borrow_mut() = false;
|
||||
window.refresh();
|
||||
})
|
||||
.detach();
|
||||
|
||||
*menu_view.borrow_mut() = Some(menu);
|
||||
|
||||
cx.refresh();
|
||||
window.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ impl Styled for Divider {
|
||||
}
|
||||
|
||||
impl RenderOnce for Divider {
|
||||
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
self.base
|
||||
.flex()
|
||||
.flex_shrink_0()
|
||||
|
||||
@@ -6,10 +6,9 @@ use crate::{
|
||||
AxisExt as _, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, Axis, Element, Entity, InteractiveElement as _,
|
||||
IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render,
|
||||
StatefulInteractiveElement, Style, Styled as _, View, ViewContext, VisualContext as _,
|
||||
WeakView, WindowContext,
|
||||
div, prelude::FluentBuilder as _, px, App, AppContext, Axis, Context, Element, Entity,
|
||||
InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels,
|
||||
Point, Render, StatefulInteractiveElement, Style, Styled as _, WeakEntity, Window,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
@@ -56,7 +55,7 @@ impl DockPlacement {
|
||||
/// This is unlike Panel, it can't be move or add any other panel.
|
||||
pub struct Dock {
|
||||
pub(super) placement: DockPlacement,
|
||||
dock_area: WeakView<DockArea>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
pub(crate) panel: DockItem,
|
||||
/// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.
|
||||
pub(super) size: Pixels,
|
||||
@@ -71,12 +70,13 @@ pub struct Dock {
|
||||
|
||||
impl Dock {
|
||||
pub(crate) fn new(
|
||||
dock_area: WeakView<DockArea>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
placement: DockPlacement,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let panel = cx.new_view(|cx| {
|
||||
let mut tab = TabPanel::new(None, dock_area.clone(), cx);
|
||||
let panel = cx.new(|cx| {
|
||||
let mut tab = TabPanel::new(None, dock_area.clone(), window, cx);
|
||||
tab.closeable = false;
|
||||
tab
|
||||
});
|
||||
@@ -87,7 +87,7 @@ impl Dock {
|
||||
view: panel.clone(),
|
||||
};
|
||||
|
||||
Self::subscribe_panel_events(dock_area.clone(), &panel, cx);
|
||||
Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
|
||||
|
||||
Self {
|
||||
placement,
|
||||
@@ -100,22 +100,39 @@ impl Dock {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self::new(dock_area, DockPlacement::Left, cx)
|
||||
pub fn left(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(dock_area, DockPlacement::Left, window, cx)
|
||||
}
|
||||
|
||||
pub fn bottom(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self::new(dock_area, DockPlacement::Bottom, cx)
|
||||
pub fn bottom(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(dock_area, DockPlacement::Bottom, window, cx)
|
||||
}
|
||||
|
||||
pub fn right(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self::new(dock_area, DockPlacement::Right, cx)
|
||||
pub fn right(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(dock_area, DockPlacement::Right, window, cx)
|
||||
}
|
||||
|
||||
/// Update the Dock to be collapsible or not.
|
||||
///
|
||||
/// And if the Dock is not collapsible, it will be open.
|
||||
pub fn set_collapsible(&mut self, collapsible: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_collapsible(
|
||||
&mut self,
|
||||
collapsible: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.collapsible = collapsible;
|
||||
if !collapsible {
|
||||
self.open = true
|
||||
@@ -124,25 +141,26 @@ impl Dock {
|
||||
}
|
||||
|
||||
pub(super) fn from_state(
|
||||
dock_area: WeakView<DockArea>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
placement: DockPlacement,
|
||||
size: Pixels,
|
||||
panel: DockItem,
|
||||
open: bool,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
Self::subscribe_panel_events(dock_area.clone(), &panel, cx);
|
||||
Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
|
||||
|
||||
if !open {
|
||||
match panel.clone() {
|
||||
DockItem::Tabs { view, .. } => {
|
||||
view.update(cx, |panel, cx| {
|
||||
panel.set_collapsed(true, cx);
|
||||
panel.set_collapsed(true, window, cx);
|
||||
});
|
||||
}
|
||||
DockItem::Split { items, .. } => {
|
||||
for item in items {
|
||||
item.set_collapsed(true, cx);
|
||||
item.set_collapsed(true, window, cx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -161,30 +179,31 @@ impl Dock {
|
||||
}
|
||||
|
||||
fn subscribe_panel_events(
|
||||
dock_area: WeakView<DockArea>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
panel: &DockItem,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
match panel {
|
||||
DockItem::Tabs { view, .. } => {
|
||||
cx.defer({
|
||||
window.defer(cx, {
|
||||
let view = view.clone();
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
this.subscribe_panel(&view, cx);
|
||||
this.subscribe_panel(&view, window, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
DockItem::Split { items, view, .. } => {
|
||||
for item in items {
|
||||
Self::subscribe_panel_events(dock_area.clone(), item, cx);
|
||||
Self::subscribe_panel_events(dock_area.clone(), item, window, cx);
|
||||
}
|
||||
cx.defer({
|
||||
window.defer(cx, {
|
||||
let view = view.clone();
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
this.subscribe_panel(&view, cx);
|
||||
this.subscribe_panel(&view, window, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -195,7 +214,7 @@ impl Dock {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_panel(&mut self, panel: DockItem, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_panel(&mut self, panel: DockItem, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.panel = panel;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -204,8 +223,8 @@ impl Dock {
|
||||
self.open
|
||||
}
|
||||
|
||||
pub fn toggle_open(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.set_open(!self.open, cx);
|
||||
pub fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.set_open(!self.open, window, cx);
|
||||
}
|
||||
|
||||
/// Returns the size of the Dock, the size is means the width or height of
|
||||
@@ -216,31 +235,40 @@ impl Dock {
|
||||
}
|
||||
|
||||
/// Set the size of the Dock.
|
||||
pub fn set_size(&mut self, size: Pixels, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_size(&mut self, size: Pixels, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.size = size.max(PANEL_MIN_SIZE);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the open state of the Dock.
|
||||
pub fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_open(&mut self, open: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.open = open;
|
||||
let item = self.panel.clone();
|
||||
cx.defer(move |_, cx| {
|
||||
item.set_collapsed(!open, cx);
|
||||
cx.defer_in(window, move |_, window, cx| {
|
||||
item.set_collapsed(!open, window, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Add item to the Dock.
|
||||
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
|
||||
self.panel.add_panel(panel, &self.dock_area, cx);
|
||||
pub fn add_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.panel.add_panel(panel, &self.dock_area, window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_resize_handle(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_resize_handle(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let axis = self.placement.axis();
|
||||
let neg_offset = -HANDLE_PADDING;
|
||||
let view = cx.view().clone();
|
||||
let view = cx.model().clone();
|
||||
|
||||
div()
|
||||
.id("resize-handle")
|
||||
@@ -279,15 +307,20 @@ impl Dock {
|
||||
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
|
||||
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
|
||||
)
|
||||
.on_drag(ResizePanel {}, move |info, _, cx| {
|
||||
.on_drag(ResizePanel {}, move |info, _, _, cx| {
|
||||
cx.stop_propagation();
|
||||
view.update(cx, |view, _| {
|
||||
view.is_resizing = true;
|
||||
});
|
||||
cx.new_view(|_| info.clone())
|
||||
cx.new(|_| info.clone())
|
||||
})
|
||||
}
|
||||
fn resize(&mut self, mouse_position: Point<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
fn resize(
|
||||
&mut self,
|
||||
mouse_position: Point<Pixels>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !self.is_resizing {
|
||||
return;
|
||||
}
|
||||
@@ -303,7 +336,7 @@ impl Dock {
|
||||
|
||||
// Get the size of the left dock if it's open and not the current dock
|
||||
if let Some(left_dock) = &dock_area.left_dock {
|
||||
if left_dock.entity_id() != cx.view().entity_id() {
|
||||
if left_dock.entity_id() != cx.model().entity_id() {
|
||||
let left_dock_read = left_dock.read(cx);
|
||||
if left_dock_read.is_open() {
|
||||
left_dock_size = left_dock_read.size;
|
||||
@@ -313,7 +346,7 @@ impl Dock {
|
||||
|
||||
// Get the size of the right dock if it's open and not the current dock
|
||||
if let Some(right_dock) = &dock_area.right_dock {
|
||||
if right_dock.entity_id() != cx.view().entity_id() {
|
||||
if right_dock.entity_id() != cx.model().entity_id() {
|
||||
let right_dock_read = right_dock.read(cx);
|
||||
if right_dock_read.is_open() {
|
||||
right_dock_size = right_dock_read.size;
|
||||
@@ -346,13 +379,13 @@ impl Dock {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn done_resizing(&mut self, _: &mut ViewContext<Self>) {
|
||||
fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.is_resizing = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Dock {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
|
||||
if !self.open && !self.placement.is_bottom() {
|
||||
return div();
|
||||
}
|
||||
@@ -374,15 +407,15 @@ impl Render for Dock {
|
||||
DockItem::Tabs { view, .. } => this.child(view.clone()),
|
||||
DockItem::Panel { view, .. } => this.child(view.clone().view()),
|
||||
})
|
||||
.child(self.render_resize_handle(cx))
|
||||
.child(self.render_resize_handle(window, cx))
|
||||
.child(DockElement {
|
||||
view: cx.view().clone(),
|
||||
view: cx.model().clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct DockElement {
|
||||
view: View<Dock>,
|
||||
view: Entity<Dock>,
|
||||
}
|
||||
|
||||
impl IntoElement for DockElement {
|
||||
@@ -404,9 +437,10 @@ impl Element for DockElement {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
(cx.request_layout(Style::default(), None), ())
|
||||
(window.request_layout(Style::default(), None, cx), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -414,7 +448,8 @@ impl Element for DockElement {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut WindowContext,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
}
|
||||
|
||||
@@ -424,23 +459,24 @@ impl Element for DockElement {
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut gpui::Window,
|
||||
_: &mut App,
|
||||
) {
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
move |e: &MouseMoveEvent, phase, cx| {
|
||||
move |e: &MouseMoveEvent, phase, window, cx| {
|
||||
if phase.bubble() {
|
||||
view.update(cx, |view, cx| view.resize(e.position, cx))
|
||||
view.update(cx, |view, cx| view.resize(e.position, window, cx))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// When any mouse up, stop dragging
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
move |_: &MouseUpEvent, phase, cx| {
|
||||
move |_: &MouseUpEvent, phase, window, cx| {
|
||||
if phase.bubble() {
|
||||
view.update(cx, |view, cx| view.done_resizing(cx));
|
||||
view.update(cx, |view, cx| view.done_resizing(window, cx));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
use super::PanelEvent;
|
||||
use crate::{
|
||||
dock_area::panel::Panel,
|
||||
dock_area::state::PanelState,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, EventEmitter, FocusHandle, FocusableView, ParentElement as _, Render, SharedString,
|
||||
Styled as _, WindowContext,
|
||||
};
|
||||
|
||||
pub(crate) struct InvalidPanel {
|
||||
name: SharedString,
|
||||
focus_handle: FocusHandle,
|
||||
old_state: PanelState,
|
||||
}
|
||||
|
||||
impl InvalidPanel {
|
||||
pub(crate) fn new(name: &str, state: PanelState, cx: &mut WindowContext) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
name: SharedString::from(name.to_owned()),
|
||||
old_state: state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for InvalidPanel {
|
||||
fn panel_id(&self) -> SharedString {
|
||||
"InvalidPanel".into()
|
||||
}
|
||||
|
||||
fn dump(&self, _cx: &AppContext) -> PanelState {
|
||||
self.old_state.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for InvalidPanel {}
|
||||
|
||||
impl FocusableView for InvalidPanel {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for InvalidPanel {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||
gpui::div()
|
||||
.size_full()
|
||||
.my_6()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
.child(format!(
|
||||
"The `{}` panel type is not registered in PanelRegistry.",
|
||||
self.name.clone()
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,20 @@ use crate::dock_area::{
|
||||
dock::{Dock, DockPlacement},
|
||||
panel::{Panel, PanelEvent, PanelStyle, PanelView},
|
||||
stack_panel::StackPanel,
|
||||
state::{DockAreaState, DockState},
|
||||
tab_panel::TabPanel,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds,
|
||||
Edges, Entity as _, EntityId, EventEmitter, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, App, AppContext, Axis,
|
||||
Bounds, Context, Edges, Entity, EntityId, EventEmitter, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window,
|
||||
};
|
||||
use panel::PanelRegistry;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod dock;
|
||||
pub mod invalid_panel;
|
||||
pub mod panel;
|
||||
pub mod stack_panel;
|
||||
pub mod state;
|
||||
pub mod tab_panel;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.set_global(PanelRegistry::new());
|
||||
}
|
||||
|
||||
actions!(dock, [ToggleZoom, ClosePanel]);
|
||||
|
||||
pub enum DockEvent {
|
||||
@@ -50,11 +40,11 @@ pub struct DockArea {
|
||||
toggle_button_panels: Edges<Option<EntityId>>,
|
||||
|
||||
/// The left dock of the dock_area.
|
||||
left_dock: Option<View<Dock>>,
|
||||
left_dock: Option<Entity<Dock>>,
|
||||
/// The bottom dock of the dock_area.
|
||||
bottom_dock: Option<View<Dock>>,
|
||||
bottom_dock: Option<Entity<Dock>>,
|
||||
/// The right dock of the dock_area.
|
||||
right_dock: Option<View<Dock>>,
|
||||
right_dock: Option<Entity<Dock>>,
|
||||
/// The top zoom view of the dock_area, if any.
|
||||
zoom_view: Option<AnyView>,
|
||||
|
||||
@@ -75,13 +65,13 @@ pub enum DockItem {
|
||||
axis: Axis,
|
||||
items: Vec<DockItem>,
|
||||
sizes: Vec<Option<Pixels>>,
|
||||
view: View<StackPanel>,
|
||||
view: Entity<StackPanel>,
|
||||
},
|
||||
/// Tab layout
|
||||
Tabs {
|
||||
items: Vec<Arc<dyn PanelView>>,
|
||||
active_ix: usize,
|
||||
view: View<TabPanel>,
|
||||
view: Entity<TabPanel>,
|
||||
},
|
||||
/// Panel layout
|
||||
Panel { view: Arc<dyn PanelView> },
|
||||
@@ -92,11 +82,13 @@ impl DockItem {
|
||||
pub fn split(
|
||||
axis: Axis,
|
||||
items: Vec<DockItem>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let sizes = vec![None; items.len()];
|
||||
Self::split_with_sizes(axis, items, sizes, dock_area, cx)
|
||||
|
||||
Self::split_with_sizes(axis, items, sizes, dock_area, window, cx)
|
||||
}
|
||||
|
||||
/// Create DockItem with split layout, each item of panel have specified size.
|
||||
@@ -107,34 +99,35 @@ impl DockItem {
|
||||
axis: Axis,
|
||||
items: Vec<DockItem>,
|
||||
sizes: Vec<Option<Pixels>>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut items = items;
|
||||
|
||||
let stack_panel = cx.new_view(|cx| {
|
||||
let mut stack_panel = StackPanel::new(axis, cx);
|
||||
let stack_panel = cx.new(|cx| {
|
||||
let mut stack_panel = StackPanel::new(axis, window, cx);
|
||||
for (i, item) in items.iter_mut().enumerate() {
|
||||
let view = item.view();
|
||||
let size = sizes.get(i).copied().flatten();
|
||||
stack_panel.add_panel(view.clone(), size, dock_area.clone(), cx)
|
||||
stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
|
||||
}
|
||||
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
let view = item.view();
|
||||
let size = sizes.get(i).copied().flatten();
|
||||
stack_panel.add_panel(view.clone(), size, dock_area.clone(), cx)
|
||||
stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
|
||||
}
|
||||
|
||||
stack_panel
|
||||
});
|
||||
|
||||
cx.defer({
|
||||
window.defer(cx, {
|
||||
let stack_panel = stack_panel.clone();
|
||||
let dock_area = dock_area.clone();
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
this.subscribe_panel(&stack_panel, cx);
|
||||
this.subscribe_panel(&stack_panel, window, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -158,35 +151,38 @@ impl DockItem {
|
||||
pub fn tabs(
|
||||
items: Vec<Arc<dyn PanelView>>,
|
||||
active_ix: Option<usize>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut new_items: Vec<Arc<dyn PanelView>> = vec![];
|
||||
for item in items.into_iter() {
|
||||
new_items.push(item)
|
||||
}
|
||||
Self::new_tabs(new_items, active_ix, dock_area, cx)
|
||||
Self::new_tabs(new_items, active_ix, dock_area, window, cx)
|
||||
}
|
||||
|
||||
pub fn tab<P: Panel>(
|
||||
item: View<P>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
item: Entity<P>,
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, cx)
|
||||
Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, window, cx)
|
||||
}
|
||||
|
||||
fn new_tabs(
|
||||
items: Vec<Arc<dyn PanelView>>,
|
||||
active_ix: Option<usize>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let active_ix = active_ix.unwrap_or(0);
|
||||
let tab_panel = cx.new_view(|cx| {
|
||||
let mut tab_panel = TabPanel::new(None, dock_area.clone(), cx);
|
||||
let tab_panel = cx.new(|cx| {
|
||||
let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
|
||||
for item in items.iter() {
|
||||
tab_panel.add_panel(item.clone(), cx)
|
||||
tab_panel.add_panel(item.clone(), window, cx)
|
||||
}
|
||||
tab_panel.active_ix = active_ix;
|
||||
tab_panel
|
||||
@@ -223,14 +219,15 @@ impl DockItem {
|
||||
pub fn add_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
match self {
|
||||
Self::Tabs { view, items, .. } => {
|
||||
items.push(panel.clone());
|
||||
view.update(cx, |tab_panel, cx| {
|
||||
tab_panel.add_panel(panel, cx);
|
||||
tab_panel.add_panel(panel, window, cx);
|
||||
});
|
||||
}
|
||||
Self::Split { view, items, .. } => {
|
||||
@@ -238,34 +235,34 @@ impl DockItem {
|
||||
for item in items.iter_mut() {
|
||||
if let DockItem::Tabs { view, .. } = item {
|
||||
view.update(cx, |tab_panel, cx| {
|
||||
tab_panel.add_panel(panel.clone(), cx);
|
||||
tab_panel.add_panel(panel.clone(), window, cx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unable to find tabs, create new tabs
|
||||
let new_item = Self::tabs(vec![panel.clone()], None, dock_area, cx);
|
||||
let new_item = Self::tabs(vec![panel.clone()], None, dock_area, window, cx);
|
||||
items.push(new_item.clone());
|
||||
view.update(cx, |stack_panel, cx| {
|
||||
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), cx);
|
||||
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx);
|
||||
});
|
||||
}
|
||||
Self::Panel { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_collapsed(&self, collapsed: bool, cx: &mut WindowContext) {
|
||||
pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {
|
||||
match self {
|
||||
DockItem::Tabs { view, .. } => {
|
||||
view.update(cx, |tab_panel, cx| {
|
||||
tab_panel.set_collapsed(collapsed, cx);
|
||||
tab_panel.set_collapsed(collapsed, window, cx);
|
||||
});
|
||||
}
|
||||
DockItem::Split { items, .. } => {
|
||||
// For each child item, set collapsed state
|
||||
for item in items {
|
||||
item.set_collapsed(collapsed, cx);
|
||||
item.set_collapsed(collapsed, window, cx);
|
||||
}
|
||||
}
|
||||
DockItem::Panel { .. } => {}
|
||||
@@ -273,7 +270,7 @@ impl DockItem {
|
||||
}
|
||||
|
||||
/// Recursively traverses to find the left-most and top-most TabPanel.
|
||||
pub(crate) fn left_top_tab_panel(&self, cx: &AppContext) -> Option<View<TabPanel>> {
|
||||
pub(crate) fn left_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
|
||||
match self {
|
||||
DockItem::Tabs { view, .. } => Some(view.clone()),
|
||||
DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx),
|
||||
@@ -282,7 +279,7 @@ impl DockItem {
|
||||
}
|
||||
|
||||
/// Recursively traverses to find the right-most and top-most TabPanel.
|
||||
pub(crate) fn right_top_tab_panel(&self, cx: &AppContext) -> Option<View<TabPanel>> {
|
||||
pub(crate) fn right_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
|
||||
match self {
|
||||
DockItem::Tabs { view, .. } => Some(view.clone()),
|
||||
DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx),
|
||||
@@ -295,9 +292,10 @@ impl DockArea {
|
||||
pub fn new(
|
||||
id: impl Into<SharedString>,
|
||||
version: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Horizontal, cx));
|
||||
let stack_panel = cx.new(|cx| StackPanel::new(Axis::Horizontal, window, cx));
|
||||
|
||||
let dock_item = DockItem::Split {
|
||||
axis: Axis::Horizontal,
|
||||
@@ -321,7 +319,7 @@ impl DockArea {
|
||||
_subscriptions: vec![],
|
||||
};
|
||||
|
||||
this.subscribe_panel(&stack_panel, cx);
|
||||
this.subscribe_panel(&stack_panel, window, cx);
|
||||
|
||||
this
|
||||
}
|
||||
@@ -333,7 +331,7 @@ impl DockArea {
|
||||
}
|
||||
|
||||
/// Set version of the dock area.
|
||||
pub fn set_version(&mut self, version: usize, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_version(&mut self, version: usize, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.version = Some(version);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -341,10 +339,10 @@ impl DockArea {
|
||||
/// The DockItem as the center of the dock area.
|
||||
///
|
||||
/// This is used to render at the Center of the DockArea.
|
||||
pub fn set_center(&mut self, item: DockItem, cx: &mut ViewContext<Self>) {
|
||||
self.subscribe_item(&item, cx);
|
||||
pub fn set_center(&mut self, item: DockItem, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.subscribe_item(&item, window, cx);
|
||||
self.items = item;
|
||||
self.update_toggle_button_tab_panels(cx);
|
||||
self.update_toggle_button_tab_panels(window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -353,20 +351,21 @@ impl DockArea {
|
||||
panel: DockItem,
|
||||
size: Option<Pixels>,
|
||||
open: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.subscribe_item(&panel, cx);
|
||||
let weak_self = cx.view().downgrade();
|
||||
self.left_dock = Some(cx.new_view(|cx| {
|
||||
let mut dock = Dock::left(weak_self.clone(), cx);
|
||||
self.subscribe_item(&panel, window, cx);
|
||||
let weak_self = cx.model().downgrade();
|
||||
self.left_dock = Some(cx.new(|cx| {
|
||||
let mut dock = Dock::left(weak_self.clone(), window, cx);
|
||||
if let Some(size) = size {
|
||||
dock.set_size(size, cx);
|
||||
dock.set_size(size, window, cx);
|
||||
}
|
||||
dock.set_panel(panel, cx);
|
||||
dock.set_open(open, cx);
|
||||
dock.set_panel(panel, window, cx);
|
||||
dock.set_open(open, window, cx);
|
||||
dock
|
||||
}));
|
||||
self.update_toggle_button_tab_panels(cx);
|
||||
self.update_toggle_button_tab_panels(window, cx);
|
||||
}
|
||||
|
||||
pub fn set_bottom_dock(
|
||||
@@ -374,20 +373,21 @@ impl DockArea {
|
||||
panel: DockItem,
|
||||
size: Option<Pixels>,
|
||||
open: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.subscribe_item(&panel, cx);
|
||||
let weak_self = cx.view().downgrade();
|
||||
self.bottom_dock = Some(cx.new_view(|cx| {
|
||||
let mut dock = Dock::bottom(weak_self.clone(), cx);
|
||||
self.subscribe_item(&panel, window, cx);
|
||||
let weak_self = cx.model().downgrade();
|
||||
self.bottom_dock = Some(cx.new(|cx| {
|
||||
let mut dock = Dock::bottom(weak_self.clone(), window, cx);
|
||||
if let Some(size) = size {
|
||||
dock.set_size(size, cx);
|
||||
dock.set_size(size, window, cx);
|
||||
}
|
||||
dock.set_panel(panel, cx);
|
||||
dock.set_open(open, cx);
|
||||
dock.set_panel(panel, window, cx);
|
||||
dock.set_open(open, window, cx);
|
||||
dock
|
||||
}));
|
||||
self.update_toggle_button_tab_panels(cx);
|
||||
self.update_toggle_button_tab_panels(window, cx);
|
||||
}
|
||||
|
||||
pub fn set_right_dock(
|
||||
@@ -395,24 +395,25 @@ impl DockArea {
|
||||
panel: DockItem,
|
||||
size: Option<Pixels>,
|
||||
open: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.subscribe_item(&panel, cx);
|
||||
let weak_self = cx.view().downgrade();
|
||||
self.right_dock = Some(cx.new_view(|cx| {
|
||||
let mut dock = Dock::right(weak_self.clone(), cx);
|
||||
self.subscribe_item(&panel, window, cx);
|
||||
let weak_self = cx.model().downgrade();
|
||||
self.right_dock = Some(cx.new(|cx| {
|
||||
let mut dock = Dock::right(weak_self.clone(), window, cx);
|
||||
if let Some(size) = size {
|
||||
dock.set_size(size, cx);
|
||||
dock.set_size(size, window, cx);
|
||||
}
|
||||
dock.set_panel(panel, cx);
|
||||
dock.set_open(open, cx);
|
||||
dock.set_panel(panel, window, cx);
|
||||
dock.set_open(open, window, cx);
|
||||
dock
|
||||
}));
|
||||
self.update_toggle_button_tab_panels(cx);
|
||||
self.update_toggle_button_tab_panels(window, cx);
|
||||
}
|
||||
|
||||
/// Set locked state of the dock area, if locked, the dock area cannot be split or move, but allows to resize panels.
|
||||
pub fn set_locked(&mut self, locked: bool, _: &mut WindowContext) {
|
||||
pub fn set_locked(&mut self, locked: bool, _window: &mut Window, _cx: &mut App) {
|
||||
self.is_locked = locked;
|
||||
}
|
||||
|
||||
@@ -432,7 +433,7 @@ impl DockArea {
|
||||
}
|
||||
|
||||
/// Determine if the dock at the given placement is open.
|
||||
pub fn is_dock_open(&self, placement: DockPlacement, cx: &AppContext) -> bool {
|
||||
pub fn is_dock_open(&self, placement: DockPlacement, cx: &App) -> bool {
|
||||
match placement {
|
||||
DockPlacement::Left => self
|
||||
.left_dock
|
||||
@@ -459,29 +460,30 @@ impl DockArea {
|
||||
pub fn set_dock_collapsible(
|
||||
&mut self,
|
||||
collapsible_edges: Edges<bool>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(left_dock) = self.left_dock.as_ref() {
|
||||
left_dock.update(cx, |dock, cx| {
|
||||
dock.set_collapsible(collapsible_edges.left, cx);
|
||||
dock.set_collapsible(collapsible_edges.left, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(bottom_dock) = self.bottom_dock.as_ref() {
|
||||
bottom_dock.update(cx, |dock, cx| {
|
||||
dock.set_collapsible(collapsible_edges.bottom, cx);
|
||||
dock.set_collapsible(collapsible_edges.bottom, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(right_dock) = self.right_dock.as_ref() {
|
||||
right_dock.update(cx, |dock, cx| {
|
||||
dock.set_collapsible(collapsible_edges.right, cx);
|
||||
dock.set_collapsible(collapsible_edges.right, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if the dock at the given placement is collapsible.
|
||||
pub fn is_dock_collapsible(&self, placement: DockPlacement, cx: &AppContext) -> bool {
|
||||
pub fn is_dock_collapsible(&self, placement: DockPlacement, cx: &App) -> bool {
|
||||
match placement {
|
||||
DockPlacement::Left => self
|
||||
.left_dock
|
||||
@@ -502,7 +504,12 @@ impl DockArea {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_dock(&self, placement: DockPlacement, cx: &mut ViewContext<Self>) {
|
||||
pub fn toggle_dock(
|
||||
&self,
|
||||
placement: DockPlacement,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let dock = match placement {
|
||||
DockPlacement::Left => &self.left_dock,
|
||||
DockPlacement::Bottom => &self.bottom_dock,
|
||||
@@ -512,7 +519,7 @@ impl DockArea {
|
||||
|
||||
if let Some(dock) = dock {
|
||||
dock.update(cx, |view, cx| {
|
||||
view.toggle_open(cx);
|
||||
view.toggle_open(window, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -524,128 +531,80 @@ impl DockArea {
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
placement: DockPlacement,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let weak_self = cx.view().downgrade();
|
||||
let weak_self = cx.model().downgrade();
|
||||
match placement {
|
||||
DockPlacement::Left => {
|
||||
if let Some(dock) = self.left_dock.as_ref() {
|
||||
dock.update(cx, |dock, cx| dock.add_panel(panel, cx))
|
||||
dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
|
||||
} else {
|
||||
self.set_left_dock(
|
||||
DockItem::tabs(vec![panel], None, &weak_self, cx),
|
||||
DockItem::tabs(vec![panel], None, &weak_self, window, cx),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
DockPlacement::Bottom => {
|
||||
if let Some(dock) = self.bottom_dock.as_ref() {
|
||||
dock.update(cx, |dock, cx| dock.add_panel(panel, cx))
|
||||
dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
|
||||
} else {
|
||||
self.set_bottom_dock(
|
||||
DockItem::tabs(vec![panel], None, &weak_self, cx),
|
||||
DockItem::tabs(vec![panel], None, &weak_self, window, cx),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
DockPlacement::Right => {
|
||||
if let Some(dock) = self.right_dock.as_ref() {
|
||||
dock.update(cx, |dock, cx| dock.add_panel(panel, cx))
|
||||
dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
|
||||
} else {
|
||||
self.set_right_dock(
|
||||
DockItem::tabs(vec![panel], None, &weak_self, cx),
|
||||
DockItem::tabs(vec![panel], None, &weak_self, window, cx),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
DockPlacement::Center => {
|
||||
self.items.add_panel(panel, &cx.view().downgrade(), cx);
|
||||
self.items
|
||||
.add_panel(panel, &cx.model().downgrade(), window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the state of the DockArea from the DockAreaState.
|
||||
///
|
||||
/// See also [DockeArea::dump].
|
||||
pub fn load(&mut self, state: DockAreaState, cx: &mut ViewContext<Self>) -> Result<()> {
|
||||
self.version = state.version;
|
||||
let weak_self = cx.view().downgrade();
|
||||
|
||||
if let Some(left_dock_state) = state.left_dock {
|
||||
self.left_dock = Some(left_dock_state.to_dock(weak_self.clone(), cx));
|
||||
}
|
||||
|
||||
if let Some(right_dock_state) = state.right_dock {
|
||||
self.right_dock = Some(right_dock_state.to_dock(weak_self.clone(), cx));
|
||||
}
|
||||
|
||||
if let Some(bottom_dock_state) = state.bottom_dock {
|
||||
self.bottom_dock = Some(bottom_dock_state.to_dock(weak_self.clone(), cx));
|
||||
}
|
||||
|
||||
self.items = state.center.to_item(weak_self, cx);
|
||||
self.update_toggle_button_tab_panels(cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dump the dock panels layout to PanelState.
|
||||
///
|
||||
/// See also [DockArea::load].
|
||||
pub fn dump(&self, cx: &AppContext) -> DockAreaState {
|
||||
let root = self.items.view();
|
||||
let center = root.dump(cx);
|
||||
|
||||
let left_dock = self
|
||||
.left_dock
|
||||
.as_ref()
|
||||
.map(|dock| DockState::new(dock.clone(), cx));
|
||||
let right_dock = self
|
||||
.right_dock
|
||||
.as_ref()
|
||||
.map(|dock| DockState::new(dock.clone(), cx));
|
||||
let bottom_dock = self
|
||||
.bottom_dock
|
||||
.as_ref()
|
||||
.map(|dock| DockState::new(dock.clone(), cx));
|
||||
|
||||
DockAreaState {
|
||||
version: self.version,
|
||||
center,
|
||||
left_dock,
|
||||
right_dock,
|
||||
bottom_dock,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe event on the panels
|
||||
fn subscribe_item(&mut self, item: &DockItem, cx: &mut ViewContext<Self>) {
|
||||
fn subscribe_item(&mut self, item: &DockItem, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match item {
|
||||
DockItem::Split { items, view, .. } => {
|
||||
for item in items {
|
||||
self.subscribe_item(item, cx);
|
||||
self.subscribe_item(item, window, cx);
|
||||
}
|
||||
|
||||
self._subscriptions
|
||||
.push(cx.subscribe(view, move |_, _, event, cx| {
|
||||
self._subscriptions.push(cx.subscribe_in(
|
||||
view,
|
||||
window,
|
||||
move |_, _, event, window, cx| {
|
||||
if let PanelEvent::LayoutChanged = event {
|
||||
let dock_area = cx.view().clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let _ = cx.update(|cx| {
|
||||
dock_area.update(cx, |view, cx| {
|
||||
view.update_toggle_button_tab_panels(cx)
|
||||
});
|
||||
cx.spawn_in(window, |view, mut window| async move {
|
||||
_ = view.update_in(&mut window, |view, window, cx| {
|
||||
view.update_toggle_button_tab_panels(window, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
cx.emit(DockEvent::LayoutChanged);
|
||||
}
|
||||
}));
|
||||
},
|
||||
));
|
||||
}
|
||||
DockItem::Tabs { .. } => {
|
||||
// We subscribe to the tab panel event in StackPanel's insert_panel
|
||||
@@ -659,43 +618,43 @@ impl DockArea {
|
||||
/// Subscribe zoom event on the panel
|
||||
pub(crate) fn subscribe_panel<P: Panel>(
|
||||
&mut self,
|
||||
view: &View<P>,
|
||||
cx: &mut ViewContext<DockArea>,
|
||||
view: &Entity<P>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<DockArea>,
|
||||
) {
|
||||
let subscription = cx.subscribe(view, move |_, panel, event, cx| match event {
|
||||
PanelEvent::ZoomIn => {
|
||||
let dock_area = cx.view().clone();
|
||||
let panel = panel.clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let _ = cx.update(|cx| {
|
||||
dock_area.update(cx, |dock, cx| {
|
||||
dock.set_zoomed_in(panel, cx);
|
||||
cx.notify();
|
||||
});
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
PanelEvent::ZoomOut => {
|
||||
let dock_area = cx.view().clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let _ = cx.update(|cx| {
|
||||
dock_area.update(cx, |view, cx| view.set_zoomed_out(cx));
|
||||
});
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
PanelEvent::LayoutChanged => {
|
||||
let dock_area = cx.view().clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let _ = cx.update(|cx| {
|
||||
dock_area.update(cx, |view, cx| view.update_toggle_button_tab_panels(cx));
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
cx.emit(DockEvent::LayoutChanged);
|
||||
}
|
||||
});
|
||||
let subscription =
|
||||
cx.subscribe_in(
|
||||
view,
|
||||
window,
|
||||
move |_, panel, event, window, cx| match event {
|
||||
PanelEvent::ZoomIn => {
|
||||
let panel = panel.clone();
|
||||
cx.spawn_in(window, |view, mut window| async move {
|
||||
_ = view.update_in(&mut window, |view, window, cx| {
|
||||
view.set_zoomed_in(panel, window, cx);
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
PanelEvent::ZoomOut => cx
|
||||
.spawn_in(window, |view, mut window| async move {
|
||||
_ = view.update_in(&mut window, |view, window, cx| {
|
||||
view.set_zoomed_out(window, cx);
|
||||
});
|
||||
})
|
||||
.detach(),
|
||||
PanelEvent::LayoutChanged => {
|
||||
cx.spawn_in(window, |view, mut window| async move {
|
||||
_ = view.update_in(&mut window, |view, window, cx| {
|
||||
view.update_toggle_button_tab_panels(window, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
cx.emit(DockEvent::LayoutChanged);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
self._subscriptions.push(subscription);
|
||||
}
|
||||
@@ -705,17 +664,22 @@ impl DockArea {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
pub fn set_zoomed_in<P: Panel>(&mut self, panel: View<P>, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_zoomed_in<P: Panel>(
|
||||
&mut self,
|
||||
panel: Entity<P>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.zoom_view = Some(panel.into());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_zoomed_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_zoomed_out(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.zoom_view = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_items(&self, _cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
fn render_items(&self, _window: &mut Window, _cx: &mut Context<Self>) -> AnyElement {
|
||||
match &self.items {
|
||||
DockItem::Split { view, .. } => view.clone().into_any_element(),
|
||||
DockItem::Tabs { view, .. } => view.clone().into_any_element(),
|
||||
@@ -723,7 +687,11 @@ impl DockArea {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_toggle_button_tab_panels(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn update_toggle_button_tab_panels(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// Left toggle button
|
||||
self.toggle_button_panels.left = self
|
||||
.items
|
||||
@@ -748,8 +716,8 @@ impl DockArea {
|
||||
impl EventEmitter<DockEvent> for DockArea {}
|
||||
|
||||
impl Render for DockArea {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
|
||||
div()
|
||||
.id("dock-area")
|
||||
@@ -758,8 +726,8 @@ impl Render for DockArea {
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full(),
|
||||
@@ -790,7 +758,7 @@ impl Render for DockArea {
|
||||
div()
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.child(self.render_items(cx)),
|
||||
.child(self.render_items(window, cx)),
|
||||
)
|
||||
// Bottom Dock
|
||||
.when_some(self.bottom_dock.clone(), |this, dock| {
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
use super::DockArea;
|
||||
use crate::{
|
||||
button::Button,
|
||||
dock_area::state::{PanelInfo, PanelState},
|
||||
popup_menu::PopupMenu,
|
||||
};
|
||||
use crate::{button::Button, popup_menu::PopupMenu};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
||||
IntoElement, SharedString, View, WeakView, WindowContext,
|
||||
AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Hsla, IntoElement,
|
||||
Render, SharedString, Window,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
pub enum PanelEvent {
|
||||
ZoomIn,
|
||||
@@ -30,7 +24,7 @@ pub struct TitleStyle {
|
||||
pub foreground: Hsla,
|
||||
}
|
||||
|
||||
pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
||||
pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
|
||||
/// The name of the panel used to serialize, deserialize and identify the panel.
|
||||
///
|
||||
/// This is used to identify the panel when deserializing the panel.
|
||||
@@ -38,94 +32,84 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
||||
fn panel_id(&self) -> SharedString;
|
||||
|
||||
/// The optional facepile of the panel
|
||||
fn panel_facepile(&self, _cx: &WindowContext) -> Option<Vec<String>> {
|
||||
fn panel_facepile(&self, _cx: &App) -> Option<Vec<String>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// The title of the panel
|
||||
fn title(&self, _cx: &WindowContext) -> AnyElement {
|
||||
SharedString::from("Untitled").into_any_element()
|
||||
fn title(&self, _cx: &App) -> AnyElement {
|
||||
SharedString::from("Unamed").into_any_element()
|
||||
}
|
||||
|
||||
/// Whether the panel can be closed, default is `true`.
|
||||
fn closeable(&self, _cx: &WindowContext) -> bool {
|
||||
fn closeable(&self, _cx: &App) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Return true if the panel is zoomable, default is `false`.
|
||||
fn zoomable(&self, _cx: &WindowContext) -> bool {
|
||||
fn zoomable(&self, _cx: &App) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// The addition popup menu of the panel, default is `None`.
|
||||
fn popup_menu(&self, this: PopupMenu, _cx: &WindowContext) -> PopupMenu {
|
||||
fn popup_menu(&self, this: PopupMenu, _cx: &App) -> PopupMenu {
|
||||
this
|
||||
}
|
||||
|
||||
/// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
|
||||
fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec<Button> {
|
||||
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Dump the panel, used to serialize the panel.
|
||||
fn dump(&self, _cx: &AppContext) -> PanelState {
|
||||
PanelState::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PanelView: 'static + Send + Sync {
|
||||
fn panel_id(&self, cx: &WindowContext) -> SharedString;
|
||||
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>>;
|
||||
fn title(&self, _cx: &WindowContext) -> AnyElement;
|
||||
fn closeable(&self, cx: &WindowContext) -> bool;
|
||||
fn zoomable(&self, cx: &WindowContext) -> bool;
|
||||
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu;
|
||||
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button>;
|
||||
fn panel_id(&self, cx: &App) -> SharedString;
|
||||
fn panel_facepile(&self, cx: &App) -> Option<Vec<String>>;
|
||||
fn title(&self, cx: &App) -> AnyElement;
|
||||
fn closeable(&self, cx: &App) -> bool;
|
||||
fn zoomable(&self, cx: &App) -> bool;
|
||||
fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu;
|
||||
fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button>;
|
||||
fn view(&self) -> AnyView;
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
|
||||
fn dump(&self, cx: &AppContext) -> PanelState;
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle;
|
||||
}
|
||||
|
||||
impl<T: Panel> PanelView for View<T> {
|
||||
fn panel_id(&self, cx: &WindowContext) -> SharedString {
|
||||
impl<T: Panel> PanelView for Entity<T> {
|
||||
fn panel_id(&self, cx: &App) -> SharedString {
|
||||
self.read(cx).panel_id()
|
||||
}
|
||||
|
||||
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>> {
|
||||
fn panel_facepile(&self, cx: &App) -> Option<Vec<String>> {
|
||||
self.read(cx).panel_facepile(cx)
|
||||
}
|
||||
|
||||
fn title(&self, cx: &WindowContext) -> AnyElement {
|
||||
fn title(&self, cx: &App) -> AnyElement {
|
||||
self.read(cx).title(cx)
|
||||
}
|
||||
|
||||
fn closeable(&self, cx: &WindowContext) -> bool {
|
||||
fn closeable(&self, cx: &App) -> bool {
|
||||
self.read(cx).closeable(cx)
|
||||
}
|
||||
|
||||
fn zoomable(&self, cx: &WindowContext) -> bool {
|
||||
fn zoomable(&self, cx: &App) -> bool {
|
||||
self.read(cx).zoomable(cx)
|
||||
}
|
||||
|
||||
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu {
|
||||
fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu {
|
||||
self.read(cx).popup_menu(menu, cx)
|
||||
}
|
||||
|
||||
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button> {
|
||||
self.read(cx).toolbar_buttons(cx)
|
||||
fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button> {
|
||||
self.read(cx).toolbar_buttons(window, cx)
|
||||
}
|
||||
|
||||
fn view(&self) -> AnyView {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.read(cx).focus_handle(cx)
|
||||
}
|
||||
|
||||
fn dump(&self, cx: &AppContext) -> PanelState {
|
||||
self.read(cx).dump(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&dyn PanelView> for AnyView {
|
||||
@@ -134,7 +118,7 @@ impl From<&dyn PanelView> for AnyView {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Panel> From<&dyn PanelView> for View<T> {
|
||||
impl<T: Panel> From<&dyn PanelView> for Entity<T> {
|
||||
fn from(value: &dyn PanelView) -> Self {
|
||||
value.view().downcast::<T>().unwrap()
|
||||
}
|
||||
@@ -145,50 +129,3 @@ impl PartialEq for dyn PanelView {
|
||||
self.view() == other.view()
|
||||
}
|
||||
}
|
||||
|
||||
type Items = HashMap<
|
||||
String,
|
||||
Arc<
|
||||
dyn Fn(
|
||||
WeakView<DockArea>,
|
||||
&PanelState,
|
||||
&PanelInfo,
|
||||
&mut WindowContext,
|
||||
) -> Box<dyn PanelView>,
|
||||
>,
|
||||
>;
|
||||
|
||||
pub struct PanelRegistry {
|
||||
pub(super) items: Items,
|
||||
}
|
||||
|
||||
impl PanelRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PanelRegistry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Global for PanelRegistry {}
|
||||
|
||||
/// Register the Panel init by panel_name to global registry.
|
||||
pub fn register_panel<F>(cx: &mut AppContext, panel_id: &str, deserialize: F)
|
||||
where
|
||||
F: Fn(WeakView<DockArea>, &PanelState, &PanelInfo, &mut WindowContext) -> Box<dyn PanelView>
|
||||
+ 'static,
|
||||
{
|
||||
if cx.try_global::<PanelRegistry>().is_none() {
|
||||
cx.set_global(PanelRegistry::new());
|
||||
}
|
||||
|
||||
cx.global_mut::<PanelRegistry>()
|
||||
.items
|
||||
.insert(panel_id.to_string(), Arc::new(deserialize));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::{DockArea, PanelEvent};
|
||||
use crate::{
|
||||
dock_area::{
|
||||
panel::{Panel, PanelView},
|
||||
state::{PanelInfo, PanelState},
|
||||
tab_panel::TabPanel,
|
||||
},
|
||||
h_flex,
|
||||
@@ -13,19 +12,19 @@ use crate::{
|
||||
AxisExt as _, Placement,
|
||||
};
|
||||
use gpui::{
|
||||
prelude::FluentBuilder, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Subscription,
|
||||
View, ViewContext, VisualContext as _, WeakView,
|
||||
prelude::FluentBuilder, App, AppContext, Axis, Context, DismissEvent, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, SharedString, Styled,
|
||||
Subscription, WeakEntity, Window,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct StackPanel {
|
||||
pub(super) parent: Option<WeakView<StackPanel>>,
|
||||
pub(super) parent: Option<WeakEntity<StackPanel>>,
|
||||
pub(super) axis: Axis,
|
||||
focus_handle: FocusHandle,
|
||||
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
|
||||
panel_group: View<ResizablePanelGroup>,
|
||||
panel_group: Entity<ResizablePanelGroup>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -34,29 +33,18 @@ impl Panel for StackPanel {
|
||||
"StackPanel".into()
|
||||
}
|
||||
|
||||
fn title(&self, _cx: &gpui::WindowContext) -> gpui::AnyElement {
|
||||
fn title(&self, _cx: &App) -> gpui::AnyElement {
|
||||
"StackPanel".into_any_element()
|
||||
}
|
||||
|
||||
fn dump(&self, cx: &AppContext) -> PanelState {
|
||||
let sizes = self.panel_group.read(cx).sizes();
|
||||
let mut state = PanelState::new(self);
|
||||
for panel in &self.panels {
|
||||
state.add_child(panel.dump(cx));
|
||||
state.info = PanelInfo::stack(sizes.clone(), self.axis);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
impl StackPanel {
|
||||
pub fn new(axis: Axis, cx: &mut ViewContext<Self>) -> Self {
|
||||
let panel_group = cx.new_view(|cx| {
|
||||
pub fn new(axis: Axis, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let panel_group = cx.new(|cx| {
|
||||
if axis == Axis::Horizontal {
|
||||
h_resizable(cx)
|
||||
h_resizable(window, cx)
|
||||
} else {
|
||||
v_resizable(cx)
|
||||
v_resizable(window, cx)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,7 +70,7 @@ impl StackPanel {
|
||||
}
|
||||
|
||||
/// Return true if self or parent only have last panel.
|
||||
pub(super) fn is_last_panel(&self, cx: &AppContext) -> bool {
|
||||
pub(super) fn is_last_panel(&self, cx: &App) -> bool {
|
||||
if self.panels.len() > 1 {
|
||||
return false;
|
||||
}
|
||||
@@ -110,10 +98,11 @@ impl StackPanel {
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
size: Option<Pixels>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.insert_panel(panel, self.panels.len(), size, dock_area, cx);
|
||||
self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx);
|
||||
}
|
||||
|
||||
pub fn add_panel_at(
|
||||
@@ -121,10 +110,19 @@ impl StackPanel {
|
||||
panel: Arc<dyn PanelView>,
|
||||
placement: Placement,
|
||||
size: Option<Pixels>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.insert_panel_at(panel, self.panels_len(), placement, size, dock_area, cx);
|
||||
self.insert_panel_at(
|
||||
panel,
|
||||
self.panels_len(),
|
||||
placement,
|
||||
size,
|
||||
dock_area,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn insert_panel_at(
|
||||
@@ -133,15 +131,16 @@ impl StackPanel {
|
||||
ix: usize,
|
||||
placement: Placement,
|
||||
size: Option<Pixels>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match placement {
|
||||
Placement::Top | Placement::Left => {
|
||||
self.insert_panel_before(panel, ix, size, dock_area, cx)
|
||||
self.insert_panel_before(panel, ix, size, dock_area, window, cx)
|
||||
}
|
||||
Placement::Right | Placement::Bottom => {
|
||||
self.insert_panel_after(panel, ix, size, dock_area, cx)
|
||||
self.insert_panel_after(panel, ix, size, dock_area, window, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,10 +151,11 @@ impl StackPanel {
|
||||
panel: Arc<dyn PanelView>,
|
||||
ix: usize,
|
||||
size: Option<Pixels>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.insert_panel(panel, ix, size, dock_area, cx);
|
||||
self.insert_panel(panel, ix, size, dock_area, window, cx);
|
||||
}
|
||||
|
||||
/// Insert a panel after the index.
|
||||
@@ -164,10 +164,11 @@ impl StackPanel {
|
||||
panel: Arc<dyn PanelView>,
|
||||
ix: usize,
|
||||
size: Option<Pixels>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.insert_panel(panel, ix + 1, size, dock_area, cx);
|
||||
self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
|
||||
}
|
||||
|
||||
fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel {
|
||||
@@ -181,19 +182,21 @@ impl StackPanel {
|
||||
panel: Arc<dyn PanelView>,
|
||||
ix: usize,
|
||||
size: Option<Pixels>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// If the panel is already in the stack, return.
|
||||
if self.index_of_panel(panel.clone()).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let view = cx.view().clone();
|
||||
cx.window_context().defer({
|
||||
let view = cx.model().clone();
|
||||
|
||||
window.defer(cx, {
|
||||
let panel = panel.clone();
|
||||
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
// If the panel is a TabPanel, set its parent to this.
|
||||
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
|
||||
tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade()));
|
||||
@@ -206,9 +209,9 @@ impl StackPanel {
|
||||
// Subscribe to the panel's layout change event.
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
|
||||
this.subscribe_panel(&tab_panel, cx);
|
||||
this.subscribe_panel(&tab_panel, window, cx);
|
||||
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
|
||||
this.subscribe_panel(&stack_panel, cx);
|
||||
this.subscribe_panel(&stack_panel, window, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -222,7 +225,12 @@ impl StackPanel {
|
||||
|
||||
self.panels.insert(ix, panel.clone());
|
||||
self.panel_group.update(cx, |view, cx| {
|
||||
view.insert_child(Self::new_resizable_panel(panel.clone(), size), ix, cx)
|
||||
view.insert_child(
|
||||
Self::new_resizable_panel(panel.clone(), size),
|
||||
ix,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
@@ -230,15 +238,20 @@ impl StackPanel {
|
||||
}
|
||||
|
||||
/// Remove panel from the stack.
|
||||
pub fn remove_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
|
||||
pub fn remove_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(ix) = self.index_of_panel(panel.clone()) {
|
||||
self.panels.remove(ix);
|
||||
self.panel_group.update(cx, |view, cx| {
|
||||
view.remove_child(ix, cx);
|
||||
view.remove_child(ix, window, cx);
|
||||
});
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
self.remove_self_if_empty(cx);
|
||||
self.remove_self_if_empty(window, cx);
|
||||
} else {
|
||||
println!("Panel not found in stack panel.");
|
||||
}
|
||||
@@ -248,8 +261,9 @@ impl StackPanel {
|
||||
pub(super) fn replace_panel(
|
||||
&mut self,
|
||||
old_panel: Arc<dyn PanelView>,
|
||||
new_panel: View<StackPanel>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
new_panel: Entity<StackPanel>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
||||
self.panels[ix] = Arc::new(new_panel.clone());
|
||||
@@ -257,6 +271,7 @@ impl StackPanel {
|
||||
view.replace_child(
|
||||
Self::new_resizable_panel(Arc::new(new_panel.clone()), None),
|
||||
ix,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -265,7 +280,7 @@ impl StackPanel {
|
||||
}
|
||||
|
||||
/// If children is empty, remove self from parent view.
|
||||
pub(crate) fn remove_self_if_empty(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_root() {
|
||||
return;
|
||||
}
|
||||
@@ -274,10 +289,10 @@ impl StackPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
let view = cx.view().clone();
|
||||
let view = cx.model().clone();
|
||||
if let Some(parent) = self.parent.as_ref() {
|
||||
_ = parent.update(cx, |parent, cx| {
|
||||
parent.remove_panel(Arc::new(view.clone()), cx);
|
||||
parent.remove_panel(Arc::new(view.clone()), window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -289,8 +304,8 @@ impl StackPanel {
|
||||
pub(super) fn left_top_tab_panel(
|
||||
&self,
|
||||
check_parent: bool,
|
||||
cx: &AppContext,
|
||||
) -> Option<View<TabPanel>> {
|
||||
cx: &App,
|
||||
) -> Option<Entity<TabPanel>> {
|
||||
if check_parent {
|
||||
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
|
||||
if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {
|
||||
@@ -317,8 +332,8 @@ impl StackPanel {
|
||||
pub(super) fn right_top_tab_panel(
|
||||
&self,
|
||||
check_parent: bool,
|
||||
cx: &AppContext,
|
||||
) -> Option<View<TabPanel>> {
|
||||
cx: &App,
|
||||
) -> Option<Entity<TabPanel>> {
|
||||
if check_parent {
|
||||
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
|
||||
if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {
|
||||
@@ -347,23 +362,23 @@ impl StackPanel {
|
||||
}
|
||||
|
||||
/// Remove all panels from the stack.
|
||||
pub(super) fn remove_all_panels(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub(super) fn remove_all_panels(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.panels.clear();
|
||||
self.panel_group
|
||||
.update(cx, |view, cx| view.remove_all_children(cx));
|
||||
.update(cx, |view, cx| view.remove_all_children(window, cx));
|
||||
}
|
||||
|
||||
/// Change the axis of the stack panel.
|
||||
pub(super) fn set_axis(&mut self, axis: Axis, cx: &mut ViewContext<Self>) {
|
||||
pub(super) fn set_axis(&mut self, axis: Axis, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.axis = axis;
|
||||
self.panel_group
|
||||
.update(cx, |view, cx| view.set_axis(axis, cx));
|
||||
.update(cx, |view, cx| view.set_axis(axis, window, cx));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for StackPanel {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for StackPanel {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
@@ -373,7 +388,7 @@ impl EventEmitter<PanelEvent> for StackPanel {}
|
||||
impl EventEmitter<DismissEvent> for StackPanel {}
|
||||
|
||||
impl Render for StackPanel {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
use super::{invalid_panel::InvalidPanel, Dock, DockArea, DockItem, PanelRegistry};
|
||||
use crate::dock_area::{dock::DockPlacement, panel::Panel};
|
||||
use gpui::{
|
||||
point, px, size, AppContext, Axis, Bounds, Pixels, View, VisualContext as _, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use itertools::Itertools as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Used to serialize and deserialize the DockArea
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DockAreaState {
|
||||
/// The version is used to mark this persisted state is compatible with the current version
|
||||
/// For example, sometimes we many totally changed the structure of the Panel,
|
||||
/// then we can compare the version to decide whether we can use the state or ignore.
|
||||
#[serde(default)]
|
||||
pub version: Option<usize>,
|
||||
pub center: PanelState,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub left_dock: Option<DockState>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub right_dock: Option<DockState>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bottom_dock: Option<DockState>,
|
||||
}
|
||||
|
||||
/// Used to serialize and deserialize the Dock
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DockState {
|
||||
panel: PanelState,
|
||||
placement: DockPlacement,
|
||||
size: Pixels,
|
||||
open: bool,
|
||||
}
|
||||
|
||||
impl DockState {
|
||||
pub fn new(dock: View<Dock>, cx: &AppContext) -> Self {
|
||||
let dock = dock.read(cx);
|
||||
|
||||
Self {
|
||||
placement: dock.placement,
|
||||
size: dock.size,
|
||||
open: dock.open,
|
||||
panel: dock.panel.view().dump(cx),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the DockState to Dock
|
||||
pub fn to_dock(&self, dock_area: WeakView<DockArea>, cx: &mut WindowContext) -> View<Dock> {
|
||||
let item = self.panel.to_item(dock_area.clone(), cx);
|
||||
cx.new_view(|cx| {
|
||||
Dock::from_state(
|
||||
dock_area.clone(),
|
||||
self.placement,
|
||||
self.size,
|
||||
item,
|
||||
self.open,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to serialize and deserialize the DockerItem
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PanelState {
|
||||
pub panel_name: String,
|
||||
pub children: Vec<PanelState>,
|
||||
pub info: PanelInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TileMeta {
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub z_index: usize,
|
||||
}
|
||||
|
||||
impl Default for TileMeta {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bounds: Bounds {
|
||||
origin: point(px(10.), px(10.)),
|
||||
size: size(px(200.), px(200.)),
|
||||
},
|
||||
z_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bounds<Pixels>> for TileMeta {
|
||||
fn from(bounds: Bounds<Pixels>) -> Self {
|
||||
Self { bounds, z_index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PanelInfo {
|
||||
#[serde(rename = "stack")]
|
||||
Stack {
|
||||
sizes: Vec<Pixels>,
|
||||
axis: usize, // 0 for horizontal, 1 for vertical
|
||||
},
|
||||
#[serde(rename = "tabs")]
|
||||
Tabs { active_index: usize },
|
||||
#[serde(rename = "panel")]
|
||||
Panel(serde_json::Value),
|
||||
}
|
||||
|
||||
impl PanelInfo {
|
||||
pub fn stack(sizes: Vec<Pixels>, axis: Axis) -> Self {
|
||||
Self::Stack {
|
||||
sizes,
|
||||
axis: if axis == Axis::Horizontal { 0 } else { 1 },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tabs(active_index: usize) -> Self {
|
||||
Self::Tabs { active_index }
|
||||
}
|
||||
|
||||
pub fn panel(info: serde_json::Value) -> Self {
|
||||
Self::Panel(info)
|
||||
}
|
||||
|
||||
pub fn axis(&self) -> Option<Axis> {
|
||||
match self {
|
||||
Self::Stack { axis, .. } => Some(if *axis == 0 {
|
||||
Axis::Horizontal
|
||||
} else {
|
||||
Axis::Vertical
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sizes(&self) -> Option<&Vec<Pixels>> {
|
||||
match self {
|
||||
Self::Stack { sizes, .. } => Some(sizes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active_index(&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Tabs { active_index } => Some(*active_index),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PanelState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
panel_name: "".to_string(),
|
||||
children: Vec::new(),
|
||||
info: PanelInfo::Panel(serde_json::Value::Null),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelState {
|
||||
pub fn new<P: Panel>(panel: &P) -> Self {
|
||||
Self {
|
||||
panel_name: panel.panel_id().to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_child(&mut self, panel: PanelState) {
|
||||
self.children.push(panel);
|
||||
}
|
||||
|
||||
pub fn to_item(&self, dock_area: WeakView<DockArea>, cx: &mut WindowContext) -> DockItem {
|
||||
let info = self.info.clone();
|
||||
|
||||
let items: Vec<DockItem> = self
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| child.to_item(dock_area.clone(), cx))
|
||||
.collect();
|
||||
|
||||
match info {
|
||||
PanelInfo::Stack { sizes, axis } => {
|
||||
let axis = if axis == 0 {
|
||||
Axis::Horizontal
|
||||
} else {
|
||||
Axis::Vertical
|
||||
};
|
||||
let sizes = sizes.iter().map(|s| Some(*s)).collect_vec();
|
||||
DockItem::split_with_sizes(axis, items, sizes, &dock_area, cx)
|
||||
}
|
||||
PanelInfo::Tabs { active_index } => {
|
||||
if items.len() == 1 {
|
||||
return items[0].clone();
|
||||
}
|
||||
|
||||
let items = items
|
||||
.iter()
|
||||
.flat_map(|item| match item {
|
||||
DockItem::Tabs { items, .. } => items.clone(),
|
||||
_ => {
|
||||
// ignore invalid panels in tabs
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
DockItem::tabs(items, Some(active_index), &dock_area, cx)
|
||||
}
|
||||
PanelInfo::Panel(_) => {
|
||||
let view = if let Some(f) = cx
|
||||
.global::<PanelRegistry>()
|
||||
.items
|
||||
.get(&self.panel_name)
|
||||
.cloned()
|
||||
{
|
||||
f(dock_area.clone(), self, &info, cx)
|
||||
} else {
|
||||
// Show an invalid panel if the panel is not registered.
|
||||
Box::new(
|
||||
cx.new_view(|cx| InvalidPanel::new(&self.panel_name, self.clone(), cx)),
|
||||
)
|
||||
};
|
||||
|
||||
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
button::{Button, ButtonVariants as _},
|
||||
dock_area::{
|
||||
dock::DockPlacement,
|
||||
panel::Panel,
|
||||
state::{PanelInfo, PanelState},
|
||||
},
|
||||
dock_area::{dock::DockPlacement, panel::Panel},
|
||||
h_flex,
|
||||
popup_menu::{PopupMenu, PopupMenuExt},
|
||||
tab::{tab_bar::TabBar, Tab},
|
||||
@@ -16,11 +12,10 @@ use crate::{
|
||||
v_flex, AxisExt, IconName, Placement, Selectable, Sizable,
|
||||
};
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, px, rems, AppContext, Corner, DefiniteLength, DismissEvent,
|
||||
DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, FocusableView,
|
||||
div, img, prelude::FluentBuilder, px, rems, App, AppContext, Context, Corner, DefiniteLength,
|
||||
DismissEvent, DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement as _, IntoElement, ObjectFit, ParentElement, Pixels, Render, ScrollHandle,
|
||||
SharedString, StatefulInteractiveElement, Styled, StyledImage, View, ViewContext,
|
||||
VisualContext as _, WeakView, WindowContext,
|
||||
SharedString, StatefulInteractiveElement, Styled, StyledImage, WeakEntity, Window,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -35,17 +30,17 @@ struct TabState {
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DragPanel {
|
||||
pub(crate) panel: Arc<dyn PanelView>,
|
||||
pub(crate) tab_panel: View<TabPanel>,
|
||||
pub(crate) tab_panel: Entity<TabPanel>,
|
||||
}
|
||||
|
||||
impl DragPanel {
|
||||
pub(crate) fn new(panel: Arc<dyn PanelView>, tab_panel: View<TabPanel>) -> Self {
|
||||
pub(crate) fn new(panel: Arc<dyn PanelView>, tab_panel: Entity<TabPanel>) -> Self {
|
||||
Self { panel, tab_panel }
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DragPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.id("drag-panel")
|
||||
.cursor_grab()
|
||||
@@ -68,9 +63,9 @@ impl Render for DragPanel {
|
||||
|
||||
pub struct TabPanel {
|
||||
focus_handle: FocusHandle,
|
||||
dock_area: WeakView<DockArea>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
/// The stock_panel can be None, if is None, that means the panels can't be split or move
|
||||
stack_panel: Option<WeakView<StackPanel>>,
|
||||
stack_panel: Option<WeakEntity<StackPanel>>,
|
||||
pub(crate) panels: Vec<Arc<dyn PanelView>>,
|
||||
pub(crate) active_ix: usize,
|
||||
/// If this is true, the Panel closeable will follow the active panel's closeable,
|
||||
@@ -88,13 +83,13 @@ impl Panel for TabPanel {
|
||||
"TabPanel".into()
|
||||
}
|
||||
|
||||
fn title(&self, cx: &WindowContext) -> gpui::AnyElement {
|
||||
fn title(&self, cx: &App) -> gpui::AnyElement {
|
||||
self.active_panel()
|
||||
.map(|panel| panel.title(cx))
|
||||
.unwrap_or("Empty Tab".into_any_element())
|
||||
}
|
||||
|
||||
fn closeable(&self, cx: &WindowContext) -> bool {
|
||||
fn closeable(&self, cx: &App) -> bool {
|
||||
if !self.closeable {
|
||||
return false;
|
||||
}
|
||||
@@ -104,13 +99,13 @@ impl Panel for TabPanel {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn zoomable(&self, cx: &WindowContext) -> bool {
|
||||
fn zoomable(&self, cx: &App) -> bool {
|
||||
self.active_panel()
|
||||
.map(|panel| panel.zoomable(cx))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu {
|
||||
fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu {
|
||||
if let Some(panel) = self.active_panel() {
|
||||
panel.popup_menu(menu, cx)
|
||||
} else {
|
||||
@@ -118,29 +113,21 @@ impl Panel for TabPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button> {
|
||||
fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button> {
|
||||
if let Some(panel) = self.active_panel() {
|
||||
panel.toolbar_buttons(cx)
|
||||
panel.toolbar_buttons(window, cx)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn dump(&self, cx: &AppContext) -> PanelState {
|
||||
let mut state = PanelState::new(self);
|
||||
for panel in self.panels.iter() {
|
||||
state.add_child(panel.dump(cx));
|
||||
state.info = PanelInfo::tabs(self.active_ix);
|
||||
}
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
impl TabPanel {
|
||||
pub fn new(
|
||||
stack_panel: Option<WeakView<StackPanel>>,
|
||||
dock_area: WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
stack_panel: Option<WeakEntity<StackPanel>>,
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
@@ -156,7 +143,7 @@ impl TabPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_parent(&mut self, view: WeakView<StackPanel>) {
|
||||
pub(super) fn set_parent(&mut self, view: WeakEntity<StackPanel>) {
|
||||
self.stack_panel = Some(view);
|
||||
}
|
||||
|
||||
@@ -165,24 +152,30 @@ impl TabPanel {
|
||||
self.panels.get(self.active_ix).cloned()
|
||||
}
|
||||
|
||||
fn set_active_ix(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
fn set_active_ix(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_ix = ix;
|
||||
self.tab_bar_scroll_handle.scroll_to_item(ix);
|
||||
self.focus_active_panel(cx);
|
||||
self.focus_active_panel(window, cx);
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Add a panel to the end of the tabs
|
||||
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
|
||||
self.add_panel_with_active(panel, true, cx);
|
||||
pub fn add_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.add_panel_with_active(panel, true, window, cx);
|
||||
}
|
||||
|
||||
fn add_panel_with_active(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
active: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self
|
||||
.panels
|
||||
@@ -196,7 +189,7 @@ impl TabPanel {
|
||||
.iter()
|
||||
.position(|p| p.panel_id(cx) == panel.panel_id(cx))
|
||||
{
|
||||
self.set_active_ix(ix, cx);
|
||||
self.set_active_ix(ix, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +200,7 @@ impl TabPanel {
|
||||
|
||||
// Set the active panel to the new panel
|
||||
if active {
|
||||
self.set_active_ix(self.panels.len() - 1, cx);
|
||||
self.set_active_ix(self.panels.len() - 1, window, cx);
|
||||
}
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
@@ -220,13 +213,14 @@ impl TabPanel {
|
||||
panel: Arc<dyn PanelView>,
|
||||
placement: Placement,
|
||||
size: Option<Pixels>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.spawn(|view, mut cx| async move {
|
||||
cx.update(|cx| {
|
||||
cx.spawn_in(window, |view, mut cx| async move {
|
||||
cx.update(|window, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
view.will_split_placement = Some(placement);
|
||||
view.split_panel(panel, placement, size, cx)
|
||||
view.split_panel(panel, placement, size, window, cx)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
@@ -241,7 +235,8 @@ impl TabPanel {
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
ix: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self
|
||||
.panels
|
||||
@@ -252,47 +247,63 @@ impl TabPanel {
|
||||
}
|
||||
|
||||
self.panels.insert(ix, panel);
|
||||
self.set_active_ix(ix, cx);
|
||||
self.set_active_ix(ix, window, cx);
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Remove a panel from the tab panel
|
||||
pub fn remove_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
|
||||
self.detach_panel(panel, cx);
|
||||
self.remove_self_if_empty(cx);
|
||||
pub fn remove_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.detach_panel(panel, window, cx);
|
||||
self.remove_self_if_empty(window, cx);
|
||||
cx.emit(PanelEvent::ZoomOut);
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
}
|
||||
|
||||
fn detach_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
|
||||
fn detach_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let panel_view = panel.view();
|
||||
self.panels.retain(|p| p.view() != panel_view);
|
||||
if self.active_ix >= self.panels.len() {
|
||||
self.set_active_ix(self.panels.len().saturating_sub(1), cx)
|
||||
self.set_active_ix(self.panels.len().saturating_sub(1), window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check to remove self from the parent StackPanel, if there is no panel left
|
||||
fn remove_self_if_empty(&self, cx: &mut ViewContext<Self>) {
|
||||
fn remove_self_if_empty(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.panels.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab_view = cx.view().clone();
|
||||
let tab_view = cx.model().clone();
|
||||
|
||||
if let Some(stack_panel) = self.stack_panel.as_ref() {
|
||||
_ = stack_panel.update(cx, |view, cx| {
|
||||
view.remove_panel(Arc::new(tab_view), cx);
|
||||
view.remove_panel(Arc::new(tab_view), window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_collapsed(&mut self, collapsed: bool, cx: &mut ViewContext<Self>) {
|
||||
pub(super) fn set_collapsed(
|
||||
&mut self,
|
||||
collapsed: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.is_collapsed = collapsed;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn is_locked(&self, cx: &AppContext) -> bool {
|
||||
fn is_locked(&self, cx: &App) -> bool {
|
||||
let Some(dock_area) = self.dock_area.upgrade() else {
|
||||
return true;
|
||||
};
|
||||
@@ -309,7 +320,7 @@ impl TabPanel {
|
||||
}
|
||||
|
||||
/// Return true if self or parent only have last panel.
|
||||
fn is_last_panel(&self, cx: &AppContext) -> bool {
|
||||
fn is_last_panel(&self, cx: &App) -> bool {
|
||||
if let Some(parent) = &self.stack_panel {
|
||||
if let Some(stack_panel) = parent.upgrade() {
|
||||
if !stack_panel.read(cx).is_last_panel(cx) {
|
||||
@@ -324,21 +335,26 @@ impl TabPanel {
|
||||
/// Return true if the tab panel is draggable.
|
||||
///
|
||||
/// E.g. if the parent and self only have one panel, it is not draggable.
|
||||
fn draggable(&self, cx: &AppContext) -> bool {
|
||||
fn draggable(&self, cx: &App) -> bool {
|
||||
!self.is_locked(cx) && !self.is_last_panel(cx)
|
||||
}
|
||||
|
||||
/// Return true if the tab panel is droppable.
|
||||
///
|
||||
/// E.g. if the tab panel is locked, it is not droppable.
|
||||
fn droppable(&self, cx: &AppContext) -> bool {
|
||||
fn droppable(&self, cx: &App) -> bool {
|
||||
!self.is_locked(cx)
|
||||
}
|
||||
|
||||
fn render_toolbar(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_toolbar(
|
||||
&self,
|
||||
state: TabState,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let is_zoomed = self.is_zoomed && state.zoomable;
|
||||
let view = cx.view().clone();
|
||||
let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx);
|
||||
let view = cx.model().clone();
|
||||
let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx);
|
||||
|
||||
// TODO: Do not show MenuButton if there is no menu items
|
||||
|
||||
@@ -347,7 +363,7 @@ impl TabPanel {
|
||||
.occlude()
|
||||
.items_center()
|
||||
.children(
|
||||
self.toolbar_buttons(cx)
|
||||
self.toolbar_buttons(window, cx)
|
||||
.into_iter()
|
||||
.map(|btn| btn.xsmall().ghost()),
|
||||
)
|
||||
@@ -358,9 +374,9 @@ impl TabPanel {
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.tooltip("Zoom Out")
|
||||
.on_click(
|
||||
cx.listener(|view, _, cx| view.on_action_toggle_zoom(&ToggleZoom, cx)),
|
||||
),
|
||||
.on_click(cx.listener(|view, _, window, cx| {
|
||||
view.on_action_toggle_zoom(&ToggleZoom, window, cx)
|
||||
})),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
@@ -368,7 +384,7 @@ impl TabPanel {
|
||||
.icon(IconName::Ellipsis)
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.popup_menu(move |this, cx| {
|
||||
.popup_menu(move |this, _window, cx| {
|
||||
build_popup_menu(this, cx)
|
||||
.when(state.zoomable, |this| {
|
||||
let name = if is_zoomed { "Zoom Out" } else { "Zoom In" };
|
||||
@@ -385,7 +401,8 @@ impl TabPanel {
|
||||
fn render_dock_toggle_button(
|
||||
&self,
|
||||
placement: DockPlacement,
|
||||
cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if self.is_zoomed {
|
||||
return None;
|
||||
@@ -396,7 +413,7 @@ impl TabPanel {
|
||||
return None;
|
||||
}
|
||||
|
||||
let view_entity_id = cx.view().entity_id();
|
||||
let view_entity_id = cx.model().entity_id();
|
||||
let toggle_button_panels = dock_area.toggle_button_panels;
|
||||
|
||||
// Check if current TabPanel's entity_id matches the one stored in DockArea for this placement
|
||||
@@ -454,26 +471,31 @@ impl TabPanel {
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let dock_area = self.dock_area.clone();
|
||||
move |_, _, cx| {
|
||||
move |_, _, window, cx| {
|
||||
_ = dock_area.update(cx, |dock_area, cx| {
|
||||
dock_area.toggle_dock(placement, cx);
|
||||
dock_area.toggle_dock(placement, window, cx);
|
||||
});
|
||||
}
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_title_bar(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render_title_bar(
|
||||
&self,
|
||||
state: TabState,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
|
||||
let Some(dock_area) = self.dock_area.upgrade() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let panel_style = dock_area.read(cx).panel_style;
|
||||
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, cx);
|
||||
let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, cx);
|
||||
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, cx);
|
||||
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);
|
||||
let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, window, cx);
|
||||
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, window, cx);
|
||||
|
||||
if self.panels.len() == 1 && panel_style == PanelStyle::Default {
|
||||
let panel = self.panels.first().unwrap();
|
||||
@@ -542,9 +564,9 @@ impl TabPanel {
|
||||
panel: panel.clone(),
|
||||
tab_panel: view,
|
||||
},
|
||||
|drag, _, cx| {
|
||||
|drag, _, _, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag.clone())
|
||||
cx.new(|_| drag.clone())
|
||||
},
|
||||
)
|
||||
}),
|
||||
@@ -554,7 +576,7 @@ impl TabPanel {
|
||||
.flex_shrink_0()
|
||||
.ml_1()
|
||||
.gap_1()
|
||||
.child(self.render_toolbar(state, cx))
|
||||
.child(self.render_toolbar(state, window, cx))
|
||||
.children(right_dock_button),
|
||||
)
|
||||
.into_any_element();
|
||||
@@ -594,29 +616,29 @@ impl TabPanel {
|
||||
.selected(active)
|
||||
.disabled(disabled)
|
||||
.when(!disabled, |this| {
|
||||
this.on_click(cx.listener(move |view, _, cx| {
|
||||
view.set_active_ix(ix, cx);
|
||||
this.on_click(cx.listener(move |view, _, window, cx| {
|
||||
view.set_active_ix(ix, window, cx);
|
||||
}))
|
||||
.when(state.draggable, |this| {
|
||||
this.on_drag(
|
||||
DragPanel::new(panel.clone(), view.clone()),
|
||||
|drag, _, cx| {
|
||||
|drag, _, _, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag.clone())
|
||||
cx.new(|_| drag.clone())
|
||||
},
|
||||
)
|
||||
})
|
||||
.when(state.droppable, |this| {
|
||||
this.drag_over::<DragPanel>(|this, _, cx| {
|
||||
this.drag_over::<DragPanel>(|this, _, _, cx| {
|
||||
this.rounded_l_none()
|
||||
.border_l_2()
|
||||
.border_r_0()
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
|
||||
})
|
||||
.on_drop(cx.listener(
|
||||
move |this, drag: &DragPanel, cx| {
|
||||
move |this, drag: &DragPanel, window, cx| {
|
||||
this.will_split_placement = None;
|
||||
this.on_drop(drag, Some(ix), true, cx)
|
||||
this.on_drop(drag, Some(ix), true, window, cx)
|
||||
},
|
||||
))
|
||||
})
|
||||
@@ -630,11 +652,11 @@ impl TabPanel {
|
||||
.flex_grow()
|
||||
.min_w_16()
|
||||
.when(state.droppable, |this| {
|
||||
this.drag_over::<DragPanel>(|this, _, cx| {
|
||||
this.drag_over::<DragPanel>(|this, _, _, cx| {
|
||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
|
||||
})
|
||||
.on_drop(cx.listener(
|
||||
move |this, drag: &DragPanel, cx| {
|
||||
move |this, drag: &DragPanel, window, cx| {
|
||||
this.will_split_placement = None;
|
||||
|
||||
let ix = if drag.tab_panel == view {
|
||||
@@ -643,7 +665,7 @@ impl TabPanel {
|
||||
None
|
||||
};
|
||||
|
||||
this.on_drop(drag, ix, false, cx)
|
||||
this.on_drop(drag, ix, false, window, cx)
|
||||
},
|
||||
))
|
||||
}),
|
||||
@@ -656,13 +678,18 @@ impl TabPanel {
|
||||
.h_full()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(self.render_toolbar(state, cx))
|
||||
.child(self.render_toolbar(state, window, cx))
|
||||
.when_some(right_dock_button, |this, btn| this.child(btn)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_active_panel(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_active_panel(
|
||||
&self,
|
||||
state: TabState,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
if self.is_collapsed {
|
||||
return Empty {}.into_any_element();
|
||||
}
|
||||
@@ -725,8 +752,8 @@ impl TabPanel {
|
||||
None => this.top_0().left_0().size_full(),
|
||||
})
|
||||
.group_drag_over::<DragPanel>("", |this| this.visible())
|
||||
.on_drop(cx.listener(|this, drag: &DragPanel, cx| {
|
||||
this.on_drop(drag, None, true, cx)
|
||||
.on_drop(cx.listener(|this, drag: &DragPanel, window, cx| {
|
||||
this.on_drop(drag, None, true, window, cx)
|
||||
})),
|
||||
)
|
||||
})
|
||||
@@ -736,7 +763,12 @@ impl TabPanel {
|
||||
}
|
||||
|
||||
/// Calculate the split direction based on the current mouse position
|
||||
fn on_panel_drag_move(&mut self, drag: &DragMoveEvent<DragPanel>, cx: &mut ViewContext<Self>) {
|
||||
fn on_panel_drag_move(
|
||||
&mut self,
|
||||
drag: &DragMoveEvent<DragPanel>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let bounds = drag.bounds;
|
||||
let position = drag.event.position;
|
||||
|
||||
@@ -764,10 +796,11 @@ impl TabPanel {
|
||||
drag: &DragPanel,
|
||||
ix: Option<usize>,
|
||||
active: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let panel = drag.panel.clone();
|
||||
let is_same_tab = drag.tab_panel == *cx.view();
|
||||
let is_same_tab = drag.tab_panel == cx.model();
|
||||
|
||||
// If target is same tab, and it is only one panel, do nothing.
|
||||
if is_same_tab && ix.is_none() {
|
||||
@@ -784,24 +817,24 @@ impl TabPanel {
|
||||
// We must to split it to remove_panel, unless it will be crash by error:
|
||||
// Cannot update ui::dock::tab_panel::TabPanel while it is already being updated
|
||||
if is_same_tab {
|
||||
self.detach_panel(panel.clone(), cx);
|
||||
self.detach_panel(panel.clone(), window, cx);
|
||||
} else {
|
||||
drag.tab_panel.update(cx, |view, cx| {
|
||||
view.detach_panel(panel.clone(), cx);
|
||||
view.remove_self_if_empty(cx);
|
||||
view.detach_panel(panel.clone(), window, cx);
|
||||
view.remove_self_if_empty(window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
// Insert into new tabs
|
||||
if let Some(placement) = self.will_split_placement {
|
||||
self.split_panel(panel, placement, None, cx);
|
||||
self.split_panel(panel, placement, None, window, cx);
|
||||
} else if let Some(ix) = ix {
|
||||
self.insert_panel_at(panel, ix, cx)
|
||||
self.insert_panel_at(panel, ix, window, cx)
|
||||
} else {
|
||||
self.add_panel_with_active(panel, active, cx)
|
||||
self.add_panel_with_active(panel, active, window, cx)
|
||||
}
|
||||
|
||||
self.remove_self_if_empty(cx);
|
||||
self.remove_self_if_empty(window, cx);
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
}
|
||||
|
||||
@@ -811,13 +844,14 @@ impl TabPanel {
|
||||
panel: Arc<dyn PanelView>,
|
||||
placement: Placement,
|
||||
size: Option<Pixels>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let dock_area = self.dock_area.clone();
|
||||
// wrap the panel in a TabPanel
|
||||
let new_tab_panel = cx.new_view(|cx| Self::new(None, dock_area.clone(), cx));
|
||||
let new_tab_panel = cx.new(|cx| Self::new(None, dock_area.clone(), window, cx));
|
||||
new_tab_panel.update(cx, |view, cx| {
|
||||
view.add_panel(panel, cx);
|
||||
view.add_panel(panel, window, cx);
|
||||
});
|
||||
|
||||
let stack_panel = match self.stack_panel.as_ref().and_then(|panel| panel.upgrade()) {
|
||||
@@ -829,7 +863,7 @@ impl TabPanel {
|
||||
|
||||
let ix = stack_panel
|
||||
.read(cx)
|
||||
.index_of_panel(Arc::new(cx.view().clone()))
|
||||
.index_of_panel(Arc::new(cx.model().clone()))
|
||||
.unwrap_or_default();
|
||||
|
||||
if parent_axis.is_vertical() && placement.is_vertical() {
|
||||
@@ -840,6 +874,7 @@ impl TabPanel {
|
||||
placement,
|
||||
size,
|
||||
dock_area.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -851,26 +886,27 @@ impl TabPanel {
|
||||
placement,
|
||||
size,
|
||||
dock_area.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 1. Create new StackPanel with new axis
|
||||
// 2. Move cx.view() from parent StackPanel to the new StackPanel
|
||||
// 2. Move cx.model() from parent StackPanel to the new StackPanel
|
||||
// 3. Add the new TabPanel to the new StackPanel at the correct index
|
||||
// 4. Add new StackPanel to the parent StackPanel at the correct index
|
||||
let tab_panel = cx.view().clone();
|
||||
let tab_panel = cx.model().clone();
|
||||
|
||||
// Try to use the old stack panel, not just create a new one, to avoid too many nested stack panels
|
||||
let new_stack_panel = if stack_panel.read(cx).panels_len() <= 1 {
|
||||
stack_panel.update(cx, |view, cx| {
|
||||
view.remove_all_panels(cx);
|
||||
view.set_axis(placement.axis(), cx);
|
||||
view.remove_all_panels(window, cx);
|
||||
view.set_axis(placement.axis(), window, cx);
|
||||
});
|
||||
stack_panel.clone()
|
||||
} else {
|
||||
cx.new_view(|cx| {
|
||||
let mut panel = StackPanel::new(placement.axis(), cx);
|
||||
cx.new(|cx| {
|
||||
let mut panel = StackPanel::new(placement.axis(), window, cx);
|
||||
panel.parent = Some(stack_panel.downgrade());
|
||||
panel
|
||||
})
|
||||
@@ -878,23 +914,42 @@ impl TabPanel {
|
||||
|
||||
new_stack_panel.update(cx, |view, cx| match placement {
|
||||
Placement::Left | Placement::Top => {
|
||||
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), cx);
|
||||
view.add_panel(Arc::new(tab_panel.clone()), None, dock_area.clone(), cx);
|
||||
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx);
|
||||
view.add_panel(
|
||||
Arc::new(tab_panel.clone()),
|
||||
None,
|
||||
dock_area.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
Placement::Right | Placement::Bottom => {
|
||||
view.add_panel(Arc::new(tab_panel.clone()), None, dock_area.clone(), cx);
|
||||
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), cx);
|
||||
view.add_panel(
|
||||
Arc::new(tab_panel.clone()),
|
||||
None,
|
||||
dock_area.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
if stack_panel != new_stack_panel {
|
||||
stack_panel.update(cx, |view, cx| {
|
||||
view.replace_panel(Arc::new(tab_panel.clone()), new_stack_panel.clone(), cx);
|
||||
view.replace_panel(
|
||||
Arc::new(tab_panel.clone()),
|
||||
new_stack_panel.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
cx.update(|cx| tab_panel.update(cx, |view, cx| view.remove_self_if_empty(cx)))
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
cx.update(|window, cx| {
|
||||
tab_panel.update(cx, |view, cx| view.remove_self_if_empty(window, cx))
|
||||
})
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
@@ -902,13 +957,18 @@ impl TabPanel {
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
}
|
||||
|
||||
fn focus_active_panel(&self, cx: &mut ViewContext<Self>) {
|
||||
fn focus_active_panel(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(active_panel) = self.active_panel() {
|
||||
active_panel.focus_handle(cx).focus(cx);
|
||||
active_panel.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_action_toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||
fn on_action_toggle_zoom(
|
||||
&mut self,
|
||||
_action: &ToggleZoom,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !self.zoomable(cx) {
|
||||
return;
|
||||
}
|
||||
@@ -921,15 +981,20 @@ impl TabPanel {
|
||||
self.is_zoomed = !self.is_zoomed;
|
||||
}
|
||||
|
||||
fn on_action_close_panel(&mut self, _: &ClosePanel, cx: &mut ViewContext<Self>) {
|
||||
fn on_action_close_panel(
|
||||
&mut self,
|
||||
_: &ClosePanel,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(panel) = self.active_panel() {
|
||||
self.remove_panel(panel, cx);
|
||||
self.remove_panel(panel, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for TabPanel {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
impl Focusable for TabPanel {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
if let Some(active_panel) = self.active_panel() {
|
||||
active_panel.focus_handle(cx)
|
||||
} else {
|
||||
@@ -943,7 +1008,7 @@ impl EventEmitter<DismissEvent> for TabPanel {}
|
||||
impl EventEmitter<PanelEvent> for TabPanel {}
|
||||
|
||||
impl Render for TabPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
let mut state = TabState {
|
||||
closeable: self.closeable(cx),
|
||||
@@ -962,7 +1027,7 @@ impl Render for TabPanel {
|
||||
.on_action(cx.listener(Self::on_action_close_panel))
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.child(self.render_title_bar(state, cx))
|
||||
.child(self.render_active_panel(state, cx))
|
||||
.child(self.render_title_bar(state, window, cx))
|
||||
.child(self.render_active_panel(state, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
use crate::{
|
||||
h_flex,
|
||||
input::ClearButton,
|
||||
list::{self, List, ListDelegate, ListItem},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
v_flex, Disableable, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
|
||||
v_flex, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement,
|
||||
AppContext, Bounds, ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle,
|
||||
FocusableView, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, Pixels,
|
||||
Render, SharedString, StatefulInteractiveElement, Styled, Task, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App,
|
||||
AppContext, Bounds, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement,
|
||||
Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Task, WeakEntity, Window,
|
||||
};
|
||||
|
||||
actions!(dropdown, [Up, Down, Enter, Escape]);
|
||||
|
||||
const CONTEXT: &str = "Dropdown";
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("up", Up, Some(CONTEXT)),
|
||||
KeyBinding::new("down", Down, Some(CONTEXT)),
|
||||
@@ -79,7 +78,12 @@ pub trait DropdownDelegate: Sized {
|
||||
false
|
||||
}
|
||||
|
||||
fn perform_search(&mut self, _query: &str, _cx: &mut ViewContext<Dropdown<Self>>) -> Task<()> {
|
||||
fn perform_search(
|
||||
&mut self,
|
||||
_query: &str,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Dropdown<Self>>,
|
||||
) -> Task<()> {
|
||||
Task::ready(())
|
||||
}
|
||||
}
|
||||
@@ -106,7 +110,7 @@ impl<T: DropdownItem> DropdownDelegate for Vec<T> {
|
||||
|
||||
struct DropdownListDelegate<D: DropdownDelegate + 'static> {
|
||||
delegate: D,
|
||||
dropdown: WeakView<Dropdown<D>>,
|
||||
dropdown: WeakEntity<Dropdown<D>>,
|
||||
selected_index: Option<usize>,
|
||||
}
|
||||
|
||||
@@ -116,15 +120,20 @@ where
|
||||
{
|
||||
type Item = ListItem;
|
||||
|
||||
fn items_count(&self, _: &AppContext) -> usize {
|
||||
fn items_count(&self, _: &App) -> usize {
|
||||
self.delegate.len()
|
||||
}
|
||||
|
||||
fn confirmed_index(&self, _: &AppContext) -> Option<usize> {
|
||||
fn confirmed_index(&self, _: &App) -> Option<usize> {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn render_item(&self, ix: usize, cx: &mut gpui::ViewContext<List<Self>>) -> Option<Self::Item> {
|
||||
fn render_item(
|
||||
&self,
|
||||
ix: usize,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<List<Self>>,
|
||||
) -> Option<Self::Item> {
|
||||
let selected = self.selected_index == Some(ix);
|
||||
let size = self
|
||||
.dropdown
|
||||
@@ -145,17 +154,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {
|
||||
fn cancel(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {
|
||||
let dropdown = self.dropdown.clone();
|
||||
cx.defer(move |_, cx| {
|
||||
cx.defer_in(window, move |_, window, cx| {
|
||||
_ = dropdown.update(cx, |this, cx| {
|
||||
this.open = false;
|
||||
this.focus(cx);
|
||||
this.focus(window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn confirm(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {
|
||||
fn confirm(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<List<Self>>) {
|
||||
self.selected_index = ix;
|
||||
|
||||
let selected_value = self
|
||||
@@ -164,33 +173,43 @@ where
|
||||
.map(|item| item.value().clone());
|
||||
let dropdown = self.dropdown.clone();
|
||||
|
||||
cx.defer(move |_, cx| {
|
||||
cx.defer_in(window, move |_, window, cx| {
|
||||
_ = dropdown.update(cx, |this, cx| {
|
||||
cx.emit(DropdownEvent::Confirm(selected_value.clone()));
|
||||
this.selected_value = selected_value;
|
||||
this.open = false;
|
||||
this.focus(cx);
|
||||
this.focus(window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) -> Task<()> {
|
||||
fn perform_search(
|
||||
&mut self,
|
||||
query: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<List<Self>>,
|
||||
) -> Task<()> {
|
||||
self.dropdown.upgrade().map_or(Task::ready(()), |dropdown| {
|
||||
dropdown.update(cx, |_, cx| self.delegate.perform_search(query, cx))
|
||||
dropdown.update(cx, |_, cx| self.delegate.perform_search(query, window, cx))
|
||||
})
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: Option<usize>, _: &mut ViewContext<List<Self>>) {
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: Option<usize>,
|
||||
_: &mut Window,
|
||||
_: &mut Context<List<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn render_empty(&self, cx: &mut ViewContext<List<Self>>) -> impl IntoElement {
|
||||
fn render_empty(&self, window: &mut Window, cx: &mut Context<List<Self>>) -> impl IntoElement {
|
||||
if let Some(empty) = self
|
||||
.dropdown
|
||||
.upgrade()
|
||||
.and_then(|dropdown| dropdown.read(cx).empty.as_ref())
|
||||
{
|
||||
empty(cx).into_any_element()
|
||||
empty(window, cx).into_any_element()
|
||||
} else {
|
||||
h_flex()
|
||||
.justify_center()
|
||||
@@ -206,21 +225,18 @@ pub enum DropdownEvent<D: DropdownDelegate + 'static> {
|
||||
Confirm(Option<<D::Item as DropdownItem>::Value>),
|
||||
}
|
||||
|
||||
type Empty = Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>;
|
||||
|
||||
/// A Dropdown element.
|
||||
pub struct Dropdown<D: DropdownDelegate + 'static> {
|
||||
id: ElementId,
|
||||
focus_handle: FocusHandle,
|
||||
list: View<List<DropdownListDelegate<D>>>,
|
||||
list: Entity<List<DropdownListDelegate<D>>>,
|
||||
size: Size,
|
||||
icon: Option<IconName>,
|
||||
open: bool,
|
||||
cleanable: bool,
|
||||
placeholder: Option<SharedString>,
|
||||
title_prefix: Option<SharedString>,
|
||||
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
||||
empty: Empty,
|
||||
empty: Option<Box<dyn Fn(&Window, &App) -> AnyElement + 'static>>,
|
||||
width: Length,
|
||||
menu_width: Length,
|
||||
/// Store the bounds of the input
|
||||
@@ -272,7 +288,12 @@ impl<T: DropdownItem + Clone> DropdownDelegate for SearchableVec<T> {
|
||||
true
|
||||
}
|
||||
|
||||
fn perform_search(&mut self, query: &str, _cx: &mut ViewContext<Dropdown<Self>>) -> Task<()> {
|
||||
fn perform_search(
|
||||
&mut self,
|
||||
query: &str,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Dropdown<Self>>,
|
||||
) -> Task<()> {
|
||||
self.matched_items = self
|
||||
.items
|
||||
.iter()
|
||||
@@ -301,27 +322,30 @@ where
|
||||
id: impl Into<ElementId>,
|
||||
delegate: D,
|
||||
selected_index: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let delegate = DropdownListDelegate {
|
||||
delegate,
|
||||
dropdown: cx.view().downgrade(),
|
||||
dropdown: cx.model().downgrade(),
|
||||
selected_index,
|
||||
};
|
||||
|
||||
let searchable = delegate.delegate.can_search();
|
||||
|
||||
let list = cx.new_view(|cx| {
|
||||
let mut list = List::new(delegate, cx).max_h(rems(20.));
|
||||
let list = cx.new(|cx| {
|
||||
let mut list = List::new(delegate, window, cx).max_h(rems(20.));
|
||||
if !searchable {
|
||||
list = list.no_query();
|
||||
}
|
||||
list
|
||||
});
|
||||
|
||||
cx.on_blur(&list.focus_handle(cx), Self::on_blur).detach();
|
||||
cx.on_blur(&focus_handle, Self::on_blur).detach();
|
||||
cx.on_blur(&list.focus_handle(cx), window, Self::on_blur)
|
||||
.detach();
|
||||
|
||||
cx.on_blur(&focus_handle, window, Self::on_blur).detach();
|
||||
|
||||
let mut this = Self {
|
||||
id: id.into(),
|
||||
@@ -332,7 +356,6 @@ where
|
||||
icon: None,
|
||||
selected_value: None,
|
||||
open: false,
|
||||
cleanable: false,
|
||||
title_prefix: None,
|
||||
empty: None,
|
||||
width: Length::Auto,
|
||||
@@ -340,7 +363,7 @@ where
|
||||
bounds: Bounds::default(),
|
||||
disabled: false,
|
||||
};
|
||||
this.set_selected_index(selected_index, cx);
|
||||
this.set_selected_index(selected_index, window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -378,12 +401,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Set true to show the clear button when the input field is not empty.
|
||||
pub fn cleanable(mut self) -> Self {
|
||||
self.cleanable = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the disable state for the dropdown.
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
@@ -397,42 +414,44 @@ where
|
||||
pub fn empty<E, F>(mut self, f: F) -> Self
|
||||
where
|
||||
E: IntoElement,
|
||||
F: Fn(&WindowContext) -> E + 'static,
|
||||
F: Fn(&Window, &App) -> E + 'static,
|
||||
{
|
||||
self.empty = Some(Box::new(move |cx| f(cx).into_any_element()));
|
||||
self.empty = Some(Box::new(move |window, cx| f(window, cx).into_any_element()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_selected_index(
|
||||
&mut self,
|
||||
selected_index: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.list.update(cx, |list, cx| {
|
||||
list.set_selected_index(selected_index, cx);
|
||||
list.set_selected_index(selected_index, window, cx);
|
||||
});
|
||||
self.update_selected_value(cx);
|
||||
self.update_selected_value(window, cx);
|
||||
}
|
||||
|
||||
pub fn set_selected_value(
|
||||
&mut self,
|
||||
selected_value: &<D::Item as DropdownItem>::Value,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) where
|
||||
<<D as DropdownDelegate>::Item as DropdownItem>::Value: PartialEq,
|
||||
{
|
||||
let delegate = self.list.read(cx).delegate();
|
||||
let selected_index = delegate.delegate.position(selected_value);
|
||||
self.set_selected_index(selected_index, cx);
|
||||
self.set_selected_index(selected_index, window, cx);
|
||||
}
|
||||
|
||||
pub fn selected_index(&self, cx: &WindowContext) -> Option<usize> {
|
||||
pub fn selected_index(&self, _window: &Window, cx: &App) -> Option<usize> {
|
||||
self.list.read(cx).selected_index()
|
||||
}
|
||||
|
||||
fn update_selected_value(&mut self, cx: &WindowContext) {
|
||||
fn update_selected_value(&mut self, window: &Window, cx: &App) {
|
||||
self.selected_value = self
|
||||
.selected_index(cx)
|
||||
.selected_index(window, cx)
|
||||
.and_then(|ix| self.list.read(cx).delegate().delegate.get(ix))
|
||||
.map(|item| item.value().clone());
|
||||
}
|
||||
@@ -441,13 +460,13 @@ where
|
||||
self.selected_value.as_ref()
|
||||
}
|
||||
|
||||
pub fn focus(&self, cx: &mut WindowContext) {
|
||||
self.focus_handle.focus(cx);
|
||||
pub fn focus(&self, window: &mut Window, _: &mut App) {
|
||||
self.focus_handle.focus(window);
|
||||
}
|
||||
|
||||
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
// When the dropdown and dropdown menu are both not focused, close the dropdown menu.
|
||||
if self.list.focus_handle(cx).is_focused(cx) || self.focus_handle.is_focused(cx) {
|
||||
if self.list.focus_handle(cx).is_focused(window) || self.focus_handle.is_focused(window) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -455,24 +474,24 @@ where
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||
fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.open {
|
||||
return;
|
||||
}
|
||||
self.list.focus_handle(cx).focus(cx);
|
||||
cx.dispatch_action(Box::new(list::SelectPrev));
|
||||
self.list.focus_handle(cx).focus(window);
|
||||
cx.dispatch_action(&list::SelectPrev);
|
||||
}
|
||||
|
||||
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||
fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.open {
|
||||
self.open = true;
|
||||
}
|
||||
|
||||
self.list.focus_handle(cx).focus(cx);
|
||||
cx.dispatch_action(Box::new(list::SelectNext));
|
||||
self.list.focus_handle(cx).focus(window);
|
||||
cx.dispatch_action(&list::SelectNext);
|
||||
}
|
||||
|
||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||
fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
|
||||
// Propagate the event to the parent view, for example to the Modal to support ENTER to confirm.
|
||||
cx.propagate();
|
||||
|
||||
@@ -480,22 +499,22 @@ where
|
||||
self.open = true;
|
||||
cx.notify();
|
||||
} else {
|
||||
self.list.focus_handle(cx).focus(cx);
|
||||
cx.dispatch_action(Box::new(list::Confirm));
|
||||
self.list.focus_handle(cx).focus(window);
|
||||
cx.dispatch_action(&list::Confirm);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_menu(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
fn toggle_menu(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.stop_propagation();
|
||||
|
||||
self.open = !self.open;
|
||||
if self.open {
|
||||
self.list.focus_handle(cx).focus(cx);
|
||||
self.list.focus_handle(cx).focus(window);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
||||
fn escape(&mut self, _: &Escape, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
// Propagate the event to the parent view, for example to the Modal to support ESC to close.
|
||||
cx.propagate();
|
||||
|
||||
@@ -503,13 +522,13 @@ where
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
self.set_selected_index(None, cx);
|
||||
fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.set_selected_index(None, window, cx);
|
||||
cx.emit(DropdownEvent::Confirm(None));
|
||||
}
|
||||
|
||||
fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
|
||||
let title = if let Some(selected_index) = &self.selected_index(cx) {
|
||||
fn display_title(&self, window: &Window, cx: &App) -> impl IntoElement {
|
||||
let title = if let Some(selected_index) = &self.selected_index(window, cx) {
|
||||
let title = self
|
||||
.list
|
||||
.read(cx)
|
||||
@@ -551,11 +570,11 @@ where
|
||||
|
||||
impl<D> EventEmitter<DropdownEvent<D>> for Dropdown<D> where D: DropdownDelegate + 'static {}
|
||||
impl<D> EventEmitter<DismissEvent> for Dropdown<D> where D: DropdownDelegate + 'static {}
|
||||
impl<D> FocusableView for Dropdown<D>
|
||||
impl<D> Focusable for Dropdown<D>
|
||||
where
|
||||
D: DropdownDelegate + 'static,
|
||||
{
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
if self.open {
|
||||
self.list.focus_handle(cx)
|
||||
} else {
|
||||
@@ -568,10 +587,9 @@ impl<D> Render for Dropdown<D>
|
||||
where
|
||||
D: DropdownDelegate + 'static,
|
||||
{
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let is_focused = self.focus_handle.is_focused(cx);
|
||||
let show_clean = self.cleanable && self.selected_index(cx).is_some();
|
||||
let view = cx.view().clone();
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_focused = self.focus_handle.is_focused(window);
|
||||
let view = cx.model().clone();
|
||||
let bounds = self.bounds;
|
||||
let allow_open = !(self.open || self.disabled);
|
||||
let outline_visible = self.open || is_focused && !self.disabled;
|
||||
@@ -579,7 +597,7 @@ where
|
||||
// If the size has change, set size to self.list, to change the QueryInput size.
|
||||
if self.list.read(cx).size != self.size {
|
||||
self.list
|
||||
.update(cx, |this, cx| this.set_size(self.size, cx))
|
||||
.update(cx, |this, cx| this.set_size(self.size, window, cx))
|
||||
}
|
||||
|
||||
div()
|
||||
@@ -618,7 +636,7 @@ where
|
||||
Length::Definite(l) => this.flex_none().w(l),
|
||||
Length::Auto => this.w_full(),
|
||||
})
|
||||
.when(outline_visible, |this| this.outline(cx))
|
||||
.when(outline_visible, |this| this.outline(window, cx))
|
||||
.input_size(self.size)
|
||||
.when(allow_open, |this| {
|
||||
this.on_click(cx.listener(Self::toggle_menu))
|
||||
@@ -633,18 +651,9 @@ where
|
||||
div()
|
||||
.w_full()
|
||||
.overflow_hidden()
|
||||
.child(self.display_title(cx)),
|
||||
.child(self.display_title(window, cx)),
|
||||
)
|
||||
.when(show_clean, |this| {
|
||||
this.child(ClearButton::new(cx).map(|this| {
|
||||
if self.disabled {
|
||||
this.disabled(true)
|
||||
} else {
|
||||
this.on_click(cx.listener(Self::clean))
|
||||
}
|
||||
}))
|
||||
})
|
||||
.when(!show_clean, |this| {
|
||||
.map(|this| {
|
||||
let icon = match self.icon.clone() {
|
||||
Some(icon) => icon,
|
||||
None => {
|
||||
@@ -671,8 +680,8 @@ where
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full(),
|
||||
@@ -699,13 +708,13 @@ where
|
||||
)
|
||||
.rounded(px(cx.theme().radius))
|
||||
.shadow_md()
|
||||
.on_mouse_down_out(|_, cx| {
|
||||
cx.dispatch_action(Box::new(Escape));
|
||||
.on_mouse_down_out(|_, _, cx| {
|
||||
cx.dispatch_action(&Escape);
|
||||
})
|
||||
.child(self.list.clone()),
|
||||
)
|
||||
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
this.escape(&Escape, cx);
|
||||
.on_mouse_down_out(cx.listener(|this, _, window, cx| {
|
||||
this.escape(&Escape, window, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
use gpui::{ClickEvent, Focusable, InteractiveElement, Stateful, WindowContext};
|
||||
use gpui::{App, ClickEvent, FocusableWrapper, InteractiveElement, Stateful, Window};
|
||||
|
||||
pub trait InteractiveElementExt: InteractiveElement {
|
||||
/// Set the listener for a double click event.
|
||||
fn on_double_click(
|
||||
mut self,
|
||||
listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interactivity().on_click(move |event, context| {
|
||||
self.interactivity().on_click(move |event, window, cx| {
|
||||
if event.up.click_count == 2 {
|
||||
listener(event, context);
|
||||
listener(event, window, cx);
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: InteractiveElement> InteractiveElementExt for Focusable<E> {}
|
||||
impl<E: InteractiveElement> InteractiveElementExt for FocusableWrapper<E> {}
|
||||
impl<E: InteractiveElement> InteractiveElementExt for Stateful<E> {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui::{FocusHandle, ViewContext};
|
||||
use gpui::{Context, FocusHandle, Window};
|
||||
|
||||
/// A trait for views that can cycle focus between its children.
|
||||
///
|
||||
@@ -8,18 +8,18 @@ use gpui::{FocusHandle, ViewContext};
|
||||
/// should be cycled, and the cycle will follow the order of the list.
|
||||
pub trait FocusableCycle {
|
||||
/// Returns a list of focus handles that should be cycled.
|
||||
fn cycle_focus_handles(&self, cx: &mut ViewContext<Self>) -> Vec<FocusHandle>
|
||||
fn cycle_focus_handles(&self, window: &mut Window, cx: &mut Context<Self>) -> Vec<FocusHandle>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Cycles focus between the focus handles returned by `cycle_focus_handles`.
|
||||
/// If `is_next` is `true`, it will cycle to the next focus handle, otherwise it will cycle to prev.
|
||||
fn cycle_focus(&self, is_next: bool, cx: &mut ViewContext<Self>)
|
||||
fn cycle_focus(&self, is_next: bool, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let focused_handle = cx.focused();
|
||||
let handles = self.cycle_focus_handles(cx);
|
||||
let focused_handle = window.focused(cx);
|
||||
let handles = self.cycle_focus_handles(window, cx);
|
||||
let handles = if is_next {
|
||||
handles
|
||||
} else {
|
||||
@@ -33,7 +33,7 @@ pub trait FocusableCycle {
|
||||
.nth(1)
|
||||
.unwrap_or(fallback_handle);
|
||||
|
||||
target_focus_handle.focus(cx);
|
||||
target_focus_handle.focus(window);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ use crate::{
|
||||
Sizable, Size,
|
||||
};
|
||||
use gpui::{
|
||||
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, Radians, Render, RenderOnce,
|
||||
SharedString, StyleRefinement, Styled, Svg, Transformation, View, VisualContext, WindowContext,
|
||||
prelude::FluentBuilder as _, svg, AnyElement, App, AppContext, Entity, Hsla, IntoElement,
|
||||
Radians, Render, RenderOnce, SharedString, StyleRefinement, Styled, Svg, Transformation,
|
||||
Window,
|
||||
};
|
||||
|
||||
#[derive(IntoElement, Clone)]
|
||||
@@ -174,9 +175,9 @@ impl IconName {
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Return the icon as a View<Icon>
|
||||
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
|
||||
Icon::build(self).view(cx)
|
||||
/// Return the icon as a Entity<Icon>
|
||||
pub fn view(self, window: &mut Window, cx: &mut App) -> Entity<Icon> {
|
||||
Icon::build(self).view(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +194,7 @@ impl From<IconName> for AnyElement {
|
||||
}
|
||||
|
||||
impl RenderOnce for IconName {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
Icon::build(self)
|
||||
}
|
||||
}
|
||||
@@ -251,8 +252,8 @@ impl Icon {
|
||||
}
|
||||
|
||||
/// Create a new view for the icon
|
||||
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
|
||||
cx.new_view(|_| self)
|
||||
pub fn view(self, _window: &mut Window, cx: &mut App) -> Entity<Icon> {
|
||||
cx.new(|_| self)
|
||||
}
|
||||
|
||||
pub fn transform(mut self, transformation: gpui::Transformation) -> Self {
|
||||
@@ -292,8 +293,8 @@ impl Sizable for Icon {
|
||||
}
|
||||
|
||||
impl RenderOnce for Icon {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let text_color = self.text_color.unwrap_or_else(|| cx.text_style().color);
|
||||
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let text_color = self.text_color.unwrap_or_else(|| window.text_style().color);
|
||||
|
||||
self.base
|
||||
.text_color(text_color)
|
||||
@@ -315,7 +316,11 @@ impl From<Icon> for AnyElement {
|
||||
}
|
||||
|
||||
impl Render for Icon {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let text_color = self
|
||||
.text_color
|
||||
.unwrap_or_else(|| cx.theme().base.step(cx, ColorScaleStep::ELEVEN));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{Icon, IconName, Sizable, Size};
|
||||
use gpui::{
|
||||
div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, Hsla,
|
||||
IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, WindowContext,
|
||||
div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, App,
|
||||
Hsla, IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, Window,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -48,7 +48,7 @@ impl Sizable for Indicator {
|
||||
}
|
||||
|
||||
impl RenderOnce for Indicator {
|
||||
fn render(self, _: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
div()
|
||||
.child(
|
||||
self.icon
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui::{ModelContext, Timer};
|
||||
use gpui::{Context, Timer};
|
||||
use std::time::Duration;
|
||||
|
||||
static INTERVAL: Duration = Duration::from_millis(500);
|
||||
@@ -26,11 +26,11 @@ impl BlinkCursor {
|
||||
}
|
||||
|
||||
/// Start the blinking
|
||||
pub fn start(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn start(&mut self, cx: &mut Context<Self>) {
|
||||
self.blink(self.epoch, cx);
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
||||
self.epoch = 0;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -40,7 +40,7 @@ impl BlinkCursor {
|
||||
self.epoch
|
||||
}
|
||||
|
||||
fn blink(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
|
||||
fn blink(&mut self, epoch: usize, cx: &mut Context<Self>) {
|
||||
if self.paused || epoch != self.epoch {
|
||||
return;
|
||||
}
|
||||
@@ -65,13 +65,12 @@ impl BlinkCursor {
|
||||
}
|
||||
|
||||
/// Pause the blinking, and delay 500ms to resume the blinking.
|
||||
pub fn pause(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn pause(&mut self, cx: &mut Context<Self>) {
|
||||
self.paused = true;
|
||||
cx.notify();
|
||||
|
||||
// delay 500ms to start the blinking
|
||||
let epoch = self.next_epoch();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
Timer::after(PAUSE_DELAY).await;
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
use crate::{
|
||||
button::{Button, ButtonVariants as _},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme as _},
|
||||
Icon, IconName, Sizable as _,
|
||||
};
|
||||
use gpui::{Styled, WindowContext};
|
||||
|
||||
pub(crate) struct ClearButton {}
|
||||
|
||||
impl ClearButton {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(cx: &mut WindowContext) -> Button {
|
||||
Button::new("clean")
|
||||
.icon(
|
||||
Icon::new(IconName::CircleX)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)),
|
||||
)
|
||||
.ghost()
|
||||
.xsmall()
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
use super::TextInput;
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme as _};
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
|
||||
use gpui::{
|
||||
fill, point, px, relative, size, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||
GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path, Pixels,
|
||||
Point, Style, TextRun, UnderlineStyle, View, WindowContext, WrappedLine,
|
||||
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||
Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path,
|
||||
Pixels, Point, Style, TextRun, UnderlineStyle, Window, WrappedLine,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
const RIGHT_MARGIN: Pixels = px(5.);
|
||||
const CURSOR_INSET: Pixels = px(0.5);
|
||||
const BOTTOM_MARGIN: Pixels = px(20.);
|
||||
|
||||
pub(super) struct TextElement {
|
||||
input: View<TextInput>,
|
||||
input: Entity<TextInput>,
|
||||
}
|
||||
|
||||
impl TextElement {
|
||||
pub(super) fn new(input: View<TextInput>) -> Self {
|
||||
pub(super) fn new(input: Entity<TextInput>) -> Self {
|
||||
Self { input }
|
||||
}
|
||||
|
||||
fn paint_mouse_listeners(&mut self, cx: &mut WindowContext) {
|
||||
cx.on_mouse_event({
|
||||
fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {
|
||||
window.on_mouse_event({
|
||||
let input = self.input.clone();
|
||||
|
||||
move |event: &MouseMoveEvent, _, cx| {
|
||||
move |event: &MouseMoveEvent, _, window, cx| {
|
||||
if event.pressed_button == Some(MouseButton::Left) {
|
||||
input.update(cx, |input, cx| {
|
||||
input.on_drag_move(event, cx);
|
||||
input.on_drag_move(event, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,8 @@ impl TextElement {
|
||||
lines: &[WrappedLine],
|
||||
line_height: Pixels,
|
||||
bounds: &mut Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (Option<PaintQuad>, Point<Pixels>) {
|
||||
let input = self.input.read(cx);
|
||||
let selected_range = &input.selected_range;
|
||||
@@ -101,15 +102,16 @@ impl TextElement {
|
||||
} else {
|
||||
scroll_offset.x
|
||||
};
|
||||
scroll_offset.y = if scroll_offset.y + cursor_pos.y > (bounds.size.height) {
|
||||
// cursor is out of bottom
|
||||
bounds.size.height - cursor_pos.y
|
||||
} else if scroll_offset.y + cursor_pos.y < px(0.) {
|
||||
// cursor is out of top
|
||||
scroll_offset.y - cursor_pos.y
|
||||
} else {
|
||||
scroll_offset.y
|
||||
};
|
||||
scroll_offset.y =
|
||||
if scroll_offset.y + cursor_pos.y > (bounds.size.height - BOTTOM_MARGIN) {
|
||||
// cursor is out of bottom
|
||||
bounds.size.height - BOTTOM_MARGIN - cursor_pos.y
|
||||
} else if scroll_offset.y + cursor_pos.y < px(0.) {
|
||||
// cursor is out of top
|
||||
scroll_offset.y - cursor_pos.y
|
||||
} else {
|
||||
scroll_offset.y
|
||||
};
|
||||
|
||||
if input.selection_reversed {
|
||||
if scroll_offset.x + cursor_start.x < px(0.) {
|
||||
@@ -134,15 +136,17 @@ impl TextElement {
|
||||
|
||||
bounds.origin += scroll_offset;
|
||||
|
||||
if input.show_cursor(cx) {
|
||||
if input.show_cursor(window, cx) {
|
||||
// cursor blink
|
||||
let cursor_height =
|
||||
window.text_style().font_size.to_pixels(window.rem_size()) + px(2.);
|
||||
cursor = Some(fill(
|
||||
Bounds::new(
|
||||
point(
|
||||
bounds.left() + cursor_pos.x,
|
||||
bounds.top() + cursor_pos.y + CURSOR_INSET,
|
||||
bounds.top() + cursor_pos.y + ((line_height - cursor_height) / 2.),
|
||||
),
|
||||
size(px(1.5), line_height),
|
||||
size(px(1.), cursor_height),
|
||||
),
|
||||
cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
))
|
||||
@@ -157,7 +161,8 @@ impl TextElement {
|
||||
lines: &[WrappedLine],
|
||||
line_height: Pixels,
|
||||
bounds: &mut Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<Path<Pixels>> {
|
||||
let input = self.input.read(cx);
|
||||
let selected_range = &input.selected_range;
|
||||
@@ -327,18 +332,19 @@ impl Element for TextElement {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let input = self.input.read(cx);
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
if self.input.read(cx).is_multi_line() {
|
||||
style.size.height = relative(1.).into();
|
||||
style.min_size.height = (input.rows.max(1) as f32 * cx.line_height()).into();
|
||||
style.min_size.height = (input.rows.max(1) as f32 * window.line_height()).into();
|
||||
} else {
|
||||
style.size.height = cx.line_height().into();
|
||||
style.size.height = window.line_height().into();
|
||||
};
|
||||
(cx.request_layout(style, []), ())
|
||||
(window.request_layout(style, [], cx), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -346,14 +352,15 @@ impl Element for TextElement {
|
||||
_id: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
let multi_line = self.input.read(cx).is_multi_line();
|
||||
let line_height = cx.line_height();
|
||||
let line_height = window.line_height();
|
||||
let input = self.input.read(cx);
|
||||
let text = input.text.clone();
|
||||
let placeholder = input.placeholder.clone();
|
||||
let style = cx.text_style();
|
||||
let style = window.text_style();
|
||||
let mut bounds = bounds;
|
||||
|
||||
let (display_text, text_color) = if text.is_empty() {
|
||||
@@ -406,14 +413,15 @@ impl Element for TextElement {
|
||||
vec![run]
|
||||
};
|
||||
|
||||
let font_size = style.font_size.to_pixels(cx.rem_size());
|
||||
let font_size = style.font_size.to_pixels(window.rem_size());
|
||||
|
||||
let wrap_width = if multi_line {
|
||||
Some(bounds.size.width - RIGHT_MARGIN)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let lines = cx
|
||||
let lines = window
|
||||
.text_system()
|
||||
.shape_text(display_text, font_size, &runs, wrap_width)
|
||||
.unwrap();
|
||||
@@ -450,9 +458,9 @@ impl Element for TextElement {
|
||||
// Calculate the scroll offset to keep the cursor in view
|
||||
|
||||
let (cursor, cursor_scroll_offset) =
|
||||
self.layout_cursor(&lines, line_height, &mut bounds, cx);
|
||||
self.layout_cursor(&lines, line_height, &mut bounds, window, cx);
|
||||
|
||||
let selection_path = self.layout_selections(&lines, line_height, &mut bounds, cx);
|
||||
let selection_path = self.layout_selections(&lines, line_height, &mut bounds, window, cx);
|
||||
|
||||
PrepaintState {
|
||||
bounds,
|
||||
@@ -469,37 +477,39 @@ impl Element for TextElement {
|
||||
input_bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let focus_handle = self.input.read(cx).focus_handle.clone();
|
||||
let focused = focus_handle.is_focused(cx);
|
||||
let focused = focus_handle.is_focused(window);
|
||||
let bounds = prepaint.bounds;
|
||||
let selected_range = self.input.read(cx).selected_range.clone();
|
||||
|
||||
cx.handle_input(
|
||||
window.handle_input(
|
||||
&focus_handle,
|
||||
ElementInputHandler::new(bounds, self.input.clone()),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Paint selections
|
||||
if let Some(path) = prepaint.selection_path.take() {
|
||||
cx.paint_path(path, cx.theme().accent.step(cx, ColorScaleStep::FIVE));
|
||||
window.paint_path(path, cx.theme().accent.step(cx, ColorScaleStep::FIVE));
|
||||
}
|
||||
|
||||
// Paint multi line text
|
||||
let line_height = cx.line_height();
|
||||
let line_height = window.line_height();
|
||||
let origin = bounds.origin;
|
||||
|
||||
let mut offset_y = px(0.);
|
||||
for line in prepaint.lines.iter() {
|
||||
let p = point(origin.x, origin.y + offset_y);
|
||||
_ = line.paint(p, line_height, cx);
|
||||
_ = line.paint(p, line_height, window, cx);
|
||||
offset_y += line.size(line_height).height;
|
||||
}
|
||||
|
||||
if focused {
|
||||
if let Some(cursor) = prepaint.cursor.take() {
|
||||
cx.paint_quad(cursor);
|
||||
window.paint_quad(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,6 +540,6 @@ impl Element for TextElement {
|
||||
input.scroll_size = scroll_size;
|
||||
});
|
||||
|
||||
self.paint_mouse_listeners(cx);
|
||||
self.paint_mouse_listeners(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,7 @@
|
||||
//! Based on the `Input` example from the `gpui` crate.
|
||||
//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::Cell;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use unicode_segmentation::*;
|
||||
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
actions, div, point, px, AnyElement, AppContext, Bounds, ClickEvent, ClipboardItem,
|
||||
Context as _, Entity, EventEmitter, FocusHandle, FocusableView, Half, InteractiveElement as _,
|
||||
IntoElement, KeyBinding, KeyDownEvent, Model, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle, ScrollWheelEvent,
|
||||
SharedString, Styled as _, UTF16Selection, ViewContext, ViewInputHandler, WindowContext,
|
||||
WrappedLine,
|
||||
};
|
||||
|
||||
// TODO:
|
||||
// - Press Up,Down to move cursor up, down line if multi-line
|
||||
// - Move cursor to skip line eof empty chars.
|
||||
|
||||
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement, ClearButton};
|
||||
|
||||
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement};
|
||||
use crate::{
|
||||
history::History,
|
||||
indicator::Indicator,
|
||||
@@ -32,6 +11,16 @@ use crate::{
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
Sizable, Size, StyleSized, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, point, prelude::FluentBuilder as _, px, AnyElement, App, AppContext, Bounds,
|
||||
ClickEvent, ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle,
|
||||
Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render,
|
||||
ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, UTF16Selection, Window, WrappedLine,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, ops::Range, rc::Rc};
|
||||
use unicode_segmentation::*;
|
||||
|
||||
actions!(
|
||||
input,
|
||||
@@ -80,7 +69,7 @@ pub enum InputEvent {
|
||||
|
||||
const CONTEXT: &str = "Input";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
|
||||
KeyBinding::new("delete", Delete, Some(CONTEXT)),
|
||||
@@ -154,8 +143,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
]);
|
||||
}
|
||||
|
||||
type TextInputPrefix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type TextInputSuffix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type TextInputPrefix<T> = Option<Box<dyn Fn(&mut Window, &mut Context<T>) -> AnyElement + 'static>>;
|
||||
type TextInputSuffix<T> = Option<Box<dyn Fn(&mut Window, &mut Context<T>) -> AnyElement + 'static>>;
|
||||
type Validate = Option<Box<dyn Fn(&str) -> bool + 'static>>;
|
||||
|
||||
pub struct TextInput {
|
||||
@@ -163,7 +152,7 @@ pub struct TextInput {
|
||||
pub(super) text: SharedString,
|
||||
multi_line: bool,
|
||||
pub(super) history: History<Change>,
|
||||
pub(super) blink_cursor: Model<BlinkCursor>,
|
||||
pub(super) blink_cursor: Entity<BlinkCursor>,
|
||||
pub(super) prefix: TextInputPrefix<Self>,
|
||||
pub(super) suffix: TextInputSuffix<Self>,
|
||||
pub(super) loading: bool,
|
||||
@@ -203,9 +192,9 @@ pub struct TextInput {
|
||||
impl EventEmitter<InputEvent> for TextInput {}
|
||||
|
||||
impl TextInput {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let blink_cursor = cx.new_model(|_| BlinkCursor::new());
|
||||
let blink_cursor = cx.new(|_| BlinkCursor::new());
|
||||
let history = History::new().group_interval(std::time::Duration::from_secs(1));
|
||||
let input = Self {
|
||||
focus_handle: focus_handle.clone(),
|
||||
@@ -246,11 +235,12 @@ impl TextInput {
|
||||
// Observe the blink cursor to repaint the view when it changes.
|
||||
cx.observe(&input.blink_cursor, |_, _, cx| cx.notify())
|
||||
.detach();
|
||||
|
||||
// Blink the cursor when the window is active, pause when it's not.
|
||||
cx.observe_window_activation(|input, cx| {
|
||||
if cx.is_window_active() {
|
||||
cx.observe_window_activation(window, |input, window, cx| {
|
||||
if window.is_window_active() {
|
||||
let focus_handle = input.focus_handle.clone();
|
||||
if focus_handle.is_focused(cx) {
|
||||
if focus_handle.is_focused(window) {
|
||||
input.blink_cursor.update(cx, |blink_cursor, cx| {
|
||||
blink_cursor.start(cx);
|
||||
});
|
||||
@@ -259,8 +249,8 @@ impl TextInput {
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_focus(&focus_handle, Self::on_focus).detach();
|
||||
cx.on_blur(&focus_handle, Self::on_blur).detach();
|
||||
cx.on_focus(&focus_handle, window, Self::on_focus).detach();
|
||||
cx.on_blur(&focus_handle, window, Self::on_blur).detach();
|
||||
|
||||
input
|
||||
}
|
||||
@@ -272,7 +262,7 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Called after moving the cursor. Updates preferred_x_offset if we know where the cursor now is.
|
||||
fn update_preferred_x_offset(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
fn update_preferred_x_offset(&mut self, _cx: &mut Context<Self>) {
|
||||
if let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) {
|
||||
let offset = self.cursor_offset();
|
||||
let line_height = self.last_line_height;
|
||||
@@ -314,7 +304,7 @@ impl TextInput {
|
||||
|
||||
/// Move the cursor vertically by one line (up or down) while preserving the column if possible.
|
||||
/// direction: -1 for up, +1 for down
|
||||
fn move_vertical(&mut self, direction: i32, cx: &mut ViewContext<Self>) {
|
||||
fn move_vertical(&mut self, direction: i32, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
@@ -344,7 +334,7 @@ impl TextInput {
|
||||
// Handle moving above the first line
|
||||
if direction == -1 && new_line_index == 0 && new_sub_line < 0 {
|
||||
// Move cursor to the beginning of the text
|
||||
self.move_to(0, cx);
|
||||
self.move_to(0, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -421,9 +411,14 @@ impl TextInput {
|
||||
/// Set the text of the input field.
|
||||
///
|
||||
/// And the selection_range will be reset to 0..0.
|
||||
pub fn set_text(&mut self, text: impl Into<SharedString>, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.history.ignore = true;
|
||||
self.replace_text(text, cx);
|
||||
self.replace_text(text, window, cx);
|
||||
self.history.ignore = false;
|
||||
// Ensure cursor to start when set text
|
||||
self.selected_range = 0..0;
|
||||
@@ -431,46 +426,55 @@ impl TextInput {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn replace_text(&mut self, text: impl Into<SharedString>, cx: &mut ViewContext<Self>) {
|
||||
fn replace_text(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let text: SharedString = text.into();
|
||||
let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();
|
||||
self.replace_text_in_range(Some(range), &text, cx);
|
||||
self.replace_text_in_range(Some(range), &text, window, cx);
|
||||
}
|
||||
|
||||
/// Set the disabled state of the input field.
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_disabled(&mut self, disabled: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.disabled = disabled;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the masked state of the input field.
|
||||
pub fn set_masked(&mut self, masked: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_masked(&mut self, masked: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.masked = masked;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the prefix element of the input field.
|
||||
pub fn set_prefix<F, E>(&mut self, builder: F, cx: &mut ViewContext<Self>)
|
||||
pub fn set_prefix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
F: Fn(&ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&Window, &Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.prefix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the suffix element of the input field.
|
||||
pub fn set_suffix<F, E>(&mut self, builder: F, cx: &mut ViewContext<Self>)
|
||||
pub fn set_suffix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
F: Fn(&ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&Window, &Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.suffix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the Input size
|
||||
pub fn set_size(&mut self, size: Size, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_size(&mut self, size: Size, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.size = size;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -490,20 +494,24 @@ impl TextInput {
|
||||
/// Set the prefix element of the input field, for example a search Icon.
|
||||
pub fn prefix<F, E>(mut self, builder: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.prefix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the suffix element of the input field, for example a clear button.
|
||||
pub fn suffix<F, E>(mut self, builder: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.suffix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -542,7 +550,7 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Set true to show indicator at the input right.
|
||||
pub fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_loading(&mut self, loading: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.loading = loading;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -557,130 +565,141 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Focus the input field.
|
||||
pub fn focus(&self, cx: &mut ViewContext<Self>) {
|
||||
self.focus_handle.focus(cx);
|
||||
pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.focus_handle.focus(window);
|
||||
}
|
||||
|
||||
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
||||
fn left(&mut self, _: &Left, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.start, cx)
|
||||
self.move_to(self.selected_range.start, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
||||
fn right(&mut self, _: &Right, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
||||
self.move_to(self.next_boundary(self.selected_range.end), window, cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.end, cx)
|
||||
self.move_to(self.selected_range.end, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||
fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
self.pause_blink_cursor(cx);
|
||||
self.move_vertical(-1, cx);
|
||||
self.move_vertical(-1, window, cx);
|
||||
}
|
||||
|
||||
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||
fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
self.pause_blink_cursor(cx);
|
||||
self.move_vertical(1, cx);
|
||||
self.move_vertical(1, window, cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx);
|
||||
fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), window, cx);
|
||||
}
|
||||
|
||||
fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
|
||||
fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
let offset = self.start_of_line(cx).saturating_sub(1);
|
||||
self.select_to(offset, cx);
|
||||
let offset = self.start_of_line(window, cx).saturating_sub(1);
|
||||
self.select_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
|
||||
fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
let offset = (self.end_of_line(cx) + 1).min(self.text.len());
|
||||
self.select_to(offset, cx);
|
||||
let offset = (self.end_of_line(window, cx) + 1).min(self.text.len());
|
||||
self.select_to(self.next_boundary(offset), window, cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.text.len(), cx)
|
||||
fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(0, window, cx);
|
||||
self.select_to(self.text.len(), window, cx)
|
||||
}
|
||||
|
||||
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
|
||||
fn home(&mut self, _: &Home, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
let offset = self.start_of_line(cx);
|
||||
self.move_to(offset, cx);
|
||||
let offset = self.start_of_line(window, cx);
|
||||
self.move_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
|
||||
fn end(&mut self, _: &End, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
let offset = self.end_of_line(cx);
|
||||
self.move_to(offset, cx);
|
||||
let offset = self.end_of_line(window, cx);
|
||||
self.move_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn move_to_start(&mut self, _: &MoveToStart, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
fn move_to_start(&mut self, _: &MoveToStart, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(0, window, cx);
|
||||
}
|
||||
|
||||
fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
|
||||
fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let end = self.text.len();
|
||||
self.move_to(end, cx);
|
||||
self.move_to(end, window, cx);
|
||||
}
|
||||
|
||||
fn select_to_start(&mut self, _: &SelectToStart, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(0, cx);
|
||||
fn select_to_start(&mut self, _: &SelectToStart, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(0, window, cx);
|
||||
}
|
||||
|
||||
fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
|
||||
fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let end = self.text.len();
|
||||
self.select_to(end, cx);
|
||||
self.select_to(end, window, cx);
|
||||
}
|
||||
|
||||
fn select_to_start_of_line(&mut self, _: &SelectToStartOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.start_of_line(cx);
|
||||
self.select_to(offset, cx);
|
||||
fn select_to_start_of_line(
|
||||
&mut self,
|
||||
_: &SelectToStartOfLine,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.start_of_line(window, cx);
|
||||
self.select_to(self.previous_boundary(offset), window, cx);
|
||||
}
|
||||
|
||||
fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.end_of_line(cx);
|
||||
self.select_to(offset, cx);
|
||||
fn select_to_end_of_line(
|
||||
&mut self,
|
||||
_: &SelectToEndOfLine,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.end_of_line(window, cx);
|
||||
self.select_to(self.next_boundary(offset), window, cx);
|
||||
}
|
||||
|
||||
/// Get start of line
|
||||
fn start_of_line(&mut self, cx: &mut ViewContext<Self>) -> usize {
|
||||
fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
|
||||
if self.is_single_line() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let offset = self.previous_boundary(self.cursor_offset());
|
||||
|
||||
self.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx)
|
||||
let line = self
|
||||
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
|
||||
.unwrap_or_default()
|
||||
.rfind('\n')
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0)
|
||||
.unwrap_or(0);
|
||||
line
|
||||
}
|
||||
|
||||
/// Get end of line
|
||||
fn end_of_line(&mut self, cx: &mut ViewContext<Self>) -> usize {
|
||||
fn end_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
|
||||
if self.is_single_line() {
|
||||
return self.text.len();
|
||||
}
|
||||
@@ -688,7 +707,12 @@ impl TextInput {
|
||||
let offset = self.next_boundary(self.cursor_offset());
|
||||
// ignore if offset is "\n"
|
||||
if self
|
||||
.text_for_range(self.range_to_utf16(&(offset - 1..offset)), &mut None, cx)
|
||||
.text_for_range(
|
||||
self.range_to_utf16(&(offset - 1..offset)),
|
||||
&mut None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.eq("\n")
|
||||
{
|
||||
@@ -698,6 +722,7 @@ impl TextInput {
|
||||
self.text_for_range(
|
||||
self.range_to_utf16(&(offset..self.text.len())),
|
||||
&mut None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
@@ -706,88 +731,106 @@ impl TextInput {
|
||||
.unwrap_or(self.text.len())
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||
fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), window, cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx);
|
||||
self.replace_text_in_range(None, "", window, cx);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
|
||||
fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx)
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), window, cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx);
|
||||
self.replace_text_in_range(None, "", window, cx);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn delete_to_beginning_of_line(
|
||||
&mut self,
|
||||
_: &DeleteToBeginningOfLine,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.start_of_line(cx);
|
||||
let offset = self.start_of_line(window, cx);
|
||||
self.replace_text_in_range(
|
||||
Some(self.range_to_utf16(&(offset..self.cursor_offset()))),
|
||||
"",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.end_of_line(cx);
|
||||
fn delete_to_end_of_line(
|
||||
&mut self,
|
||||
_: &DeleteToEndOfLine,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.end_of_line(window, cx);
|
||||
self.replace_text_in_range(
|
||||
Some(self.range_to_utf16(&(self.cursor_offset()..offset))),
|
||||
"",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||
fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_multi_line() {
|
||||
let is_eof = self.selected_range.end == self.text.len();
|
||||
self.replace_text_in_range(None, "\n", cx);
|
||||
self.replace_text_in_range(None, "\n", window, cx);
|
||||
|
||||
// Move cursor to the start of the next line
|
||||
let mut new_offset = self.next_boundary(self.cursor_offset()) - 1;
|
||||
if is_eof {
|
||||
new_offset += 1;
|
||||
}
|
||||
self.move_to(new_offset, cx);
|
||||
self.move_to(new_offset, window, cx);
|
||||
}
|
||||
|
||||
cx.emit(InputEvent::PressEnter);
|
||||
}
|
||||
|
||||
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
self.replace_text("", cx);
|
||||
fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.replace_text("", window, cx);
|
||||
}
|
||||
|
||||
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
|
||||
fn on_mouse_down(
|
||||
&mut self,
|
||||
event: &MouseDownEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.is_selecting = true;
|
||||
let offset = self.index_for_mouse_position(event.position, cx);
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
// Double click to select word
|
||||
if event.button == MouseButton::Left && event.click_count == 2 {
|
||||
self.select_word(offset, cx);
|
||||
self.select_word(offset, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if event.modifiers.shift {
|
||||
self.select_to(offset, cx);
|
||||
self.select_to(offset, window, cx);
|
||||
} else {
|
||||
self.move_to(offset, cx)
|
||||
self.move_to(offset, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) {
|
||||
fn on_mouse_up(&mut self, _: &MouseUpEvent, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.is_selecting = false;
|
||||
self.selected_word_range = None;
|
||||
}
|
||||
|
||||
fn on_scroll_wheel(&mut self, event: &ScrollWheelEvent, _: &mut ViewContext<Self>) {
|
||||
fn on_scroll_wheel(
|
||||
&mut self,
|
||||
event: &ScrollWheelEvent,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) {
|
||||
let delta = event.delta.pixel_delta(self.last_line_height);
|
||||
let safe_y_range =
|
||||
(-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
|
||||
@@ -801,11 +844,16 @@ impl TextInput {
|
||||
self.scroll_handle.set_offset(offset);
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
cx.show_character_palette();
|
||||
fn show_character_palette(
|
||||
&mut self,
|
||||
_: &ShowCharacterPalette,
|
||||
window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) {
|
||||
window.show_character_palette();
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||
fn copy(&mut self, _: &Copy, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -814,7 +862,7 @@ impl TextInput {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
|
||||
fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -822,27 +870,33 @@ impl TextInput {
|
||||
let range = self.range_from_utf16(&self.selected_range);
|
||||
let selected_text = self.text[range].to_string();
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
||||
self.replace_text_in_range(None, "", cx);
|
||||
self.replace_text_in_range(None, "", window, cx);
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(clipboard) = cx.read_from_clipboard() {
|
||||
let mut new_text = clipboard.text().unwrap_or_default();
|
||||
if !self.multi_line {
|
||||
new_text = new_text.replace('\n', "");
|
||||
}
|
||||
|
||||
self.replace_text_in_range(None, &new_text, cx);
|
||||
self.replace_text_in_range(None, &new_text, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_history(&mut self, range: &Range<usize>, new_text: &str, cx: &mut ViewContext<Self>) {
|
||||
fn push_history(
|
||||
&mut self,
|
||||
range: &Range<usize>,
|
||||
new_text: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.history.ignore {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_text = self
|
||||
.text_for_range(self.range_to_utf16(range), &mut None, cx)
|
||||
.text_for_range(self.range_to_utf16(range), &mut None, window, cx)
|
||||
.unwrap_or("".to_string());
|
||||
|
||||
let new_range = range.start..range.start + new_text.len();
|
||||
@@ -855,29 +909,34 @@ impl TextInput {
|
||||
));
|
||||
}
|
||||
|
||||
fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
||||
fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.history.ignore = true;
|
||||
if let Some(changes) = self.history.undo() {
|
||||
for change in changes {
|
||||
let range_utf16 = self.range_to_utf16(&change.new_range);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.old_text, cx);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.old_text, window, cx);
|
||||
}
|
||||
}
|
||||
self.history.ignore = false;
|
||||
}
|
||||
|
||||
fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
|
||||
fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.history.ignore = true;
|
||||
if let Some(changes) = self.history.redo() {
|
||||
for change in changes {
|
||||
let range_utf16 = self.range_to_utf16(&change.old_range);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.new_text, cx);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.new_text, window, cx);
|
||||
}
|
||||
}
|
||||
self.history.ignore = false;
|
||||
}
|
||||
|
||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
/// Move the cursor to the given offset.
|
||||
///
|
||||
/// The offset is the UTF-8 offset.
|
||||
///
|
||||
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
||||
fn move_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_range = offset..offset;
|
||||
self.pause_blink_cursor(cx);
|
||||
self.update_preferred_x_offset(cx);
|
||||
@@ -892,7 +951,12 @@ impl TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
fn index_for_mouse_position(&self, position: Point<Pixels>, _: &WindowContext) -> usize {
|
||||
fn index_for_mouse_position(
|
||||
&self,
|
||||
position: Point<Pixels>,
|
||||
_window: &Window,
|
||||
_cx: &App,
|
||||
) -> usize {
|
||||
// If the text is empty, always return 0
|
||||
if self.text.is_empty() {
|
||||
return 0;
|
||||
@@ -987,7 +1051,12 @@ impl TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
/// Select the text from the current cursor position to the given offset.
|
||||
///
|
||||
/// The offset is the UTF-8 offset.
|
||||
///
|
||||
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
||||
fn select_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selection_reversed {
|
||||
self.selected_range.start = offset
|
||||
} else {
|
||||
@@ -1015,7 +1084,7 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Select the word at the given offset.
|
||||
fn select_word(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
fn select_word(&mut self, offset: usize, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn is_word(c: char) -> bool {
|
||||
c.is_alphanumeric() || matches!(c, '_')
|
||||
}
|
||||
@@ -1023,10 +1092,10 @@ impl TextInput {
|
||||
let mut start = self.offset_to_utf16(offset);
|
||||
let mut end = start;
|
||||
let prev_text = self
|
||||
.text_for_range(0..start, &mut None, cx)
|
||||
.text_for_range(0..start, &mut None, window, cx)
|
||||
.unwrap_or_default();
|
||||
let next_text = self
|
||||
.text_for_range(end..self.text.len(), &mut None, cx)
|
||||
.text_for_range(end..self.text.len(), &mut None, window, cx)
|
||||
.unwrap_or_default();
|
||||
|
||||
let prev_chars = prev_text.chars().rev().peekable();
|
||||
@@ -1053,7 +1122,7 @@ impl TextInput {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn unselect(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn unselect(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_range = self.cursor_offset()..self.cursor_offset();
|
||||
cx.notify()
|
||||
}
|
||||
@@ -1112,36 +1181,46 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
|
||||
pub(crate) fn show_cursor(&self, cx: &WindowContext) -> bool {
|
||||
self.focus_handle.is_focused(cx) && self.blink_cursor.read(cx).visible()
|
||||
pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
|
||||
self.focus_handle.is_focused(window) && self.blink_cursor.read(cx).visible()
|
||||
}
|
||||
|
||||
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn on_focus(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.blink_cursor.update(cx, |cursor, cx| {
|
||||
cursor.start(cx);
|
||||
});
|
||||
cx.emit(InputEvent::Focus);
|
||||
}
|
||||
|
||||
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.unselect(cx);
|
||||
fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.unselect(window, cx);
|
||||
self.blink_cursor.update(cx, |cursor, cx| {
|
||||
cursor.stop(cx);
|
||||
});
|
||||
cx.emit(InputEvent::Blur);
|
||||
}
|
||||
|
||||
fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
|
||||
self.blink_cursor.update(cx, |cursor, cx| {
|
||||
cursor.pause(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_key_down_for_blink_cursor(&mut self, _: &KeyDownEvent, cx: &mut ViewContext<Self>) {
|
||||
fn on_key_down_for_blink_cursor(
|
||||
&mut self,
|
||||
_: &KeyDownEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.pause_blink_cursor(cx)
|
||||
}
|
||||
|
||||
pub(super) fn on_drag_move(&mut self, event: &MouseMoveEvent, cx: &mut ViewContext<Self>) {
|
||||
pub(super) fn on_drag_move(
|
||||
&mut self,
|
||||
event: &MouseMoveEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.text.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -1150,7 +1229,7 @@ impl TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.focus_handle.is_focused(cx) {
|
||||
if !self.focus_handle.is_focused(window) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1158,8 +1237,8 @@ impl TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
let offset = self.index_for_mouse_position(event.position, cx);
|
||||
self.select_to(offset, cx);
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
self.select_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn is_valid_input(&self, new_text: &str) -> bool {
|
||||
@@ -1187,12 +1266,13 @@ impl Sizable for TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewInputHandler for TextInput {
|
||||
impl EntityInputHandler for TextInput {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<String> {
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
adjusted_range.replace(self.range_to_utf16(&range));
|
||||
@@ -1202,7 +1282,8 @@ impl ViewInputHandler for TextInput {
|
||||
fn selected_text_range(
|
||||
&mut self,
|
||||
_ignore_disabled_input: bool,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<UTF16Selection> {
|
||||
Some(UTF16Selection {
|
||||
range: self.range_to_utf16(&self.selected_range),
|
||||
@@ -1210,13 +1291,17 @@ impl ViewInputHandler for TextInput {
|
||||
})
|
||||
}
|
||||
|
||||
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
fn marked_text_range(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Range<usize>> {
|
||||
self.marked_range
|
||||
.as_ref()
|
||||
.map(|range| self.range_to_utf16(range))
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
fn unmark_text(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
@@ -1224,7 +1309,8 @@ impl ViewInputHandler for TextInput {
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.disabled {
|
||||
return;
|
||||
@@ -1242,7 +1328,7 @@ impl ViewInputHandler for TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
self.push_history(&range, new_text, cx);
|
||||
self.push_history(&range, new_text, window, cx);
|
||||
self.text = pending_text;
|
||||
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
|
||||
self.marked_range.take();
|
||||
@@ -1256,7 +1342,8 @@ impl ViewInputHandler for TextInput {
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.disabled {
|
||||
return;
|
||||
@@ -1273,7 +1360,7 @@ impl ViewInputHandler for TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
self.push_history(&range, new_text, cx);
|
||||
self.push_history(&range, new_text, window, cx);
|
||||
self.text = pending_text;
|
||||
self.marked_range = Some(range.start..range.start + new_text.len());
|
||||
self.selected_range = new_selected_range_utf16
|
||||
@@ -1291,7 +1378,8 @@ impl ViewInputHandler for TextInput {
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
let line_height = self.last_line_height;
|
||||
let lines = self.last_layout.as_ref()?;
|
||||
@@ -1325,19 +1413,19 @@ impl ViewInputHandler for TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for TextInput {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for TextInput {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextInput {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
const LINE_HEIGHT: Rems = Rems(1.25);
|
||||
let focused = self.focus_handle.is_focused(cx);
|
||||
let focused = self.focus_handle.is_focused(window);
|
||||
|
||||
let prefix = self.prefix.as_ref().map(|build| build(cx));
|
||||
let suffix = self.suffix.as_ref().map(|build| build(cx));
|
||||
let prefix = self.prefix.as_ref().map(|build| build(window, cx));
|
||||
let suffix = self.suffix.as_ref().map(|build| build(window, cx));
|
||||
|
||||
div()
|
||||
.flex()
|
||||
@@ -1392,7 +1480,7 @@ impl Render for TextInput {
|
||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.rounded(px(cx.theme().radius))
|
||||
.when(cx.theme().shadow, |this| this.shadow_sm())
|
||||
.when(focused, |this| this.outline(cx))
|
||||
.when(focused, |this| this.outline(window, cx))
|
||||
.when(prefix.is_none(), |this| this.input_pl(self.size))
|
||||
.when(suffix.is_none(), |this| this.input_pr(self.size))
|
||||
})
|
||||
@@ -1404,18 +1492,14 @@ impl Render for TextInput {
|
||||
.id("TextElement")
|
||||
.flex_grow()
|
||||
.overflow_x_hidden()
|
||||
.child(TextElement::new(cx.view().clone())),
|
||||
.child(TextElement::new(cx.model().clone())),
|
||||
)
|
||||
.when(self.loading, |this| {
|
||||
this.child(Indicator::new().color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)))
|
||||
})
|
||||
.when(
|
||||
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(),
|
||||
|this| this.child(ClearButton::new(cx).on_click(cx.listener(Self::clean))),
|
||||
)
|
||||
.children(suffix)
|
||||
.when(self.is_multi_line(), |this| {
|
||||
let entity_id = cx.view().entity_id();
|
||||
let entity_id = cx.model().entity_id();
|
||||
if self.last_layout.is_some() {
|
||||
let scroll_size = self.scroll_size;
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
mod blink_cursor;
|
||||
mod change;
|
||||
mod clear_button;
|
||||
mod element;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod input;
|
||||
|
||||
pub(crate) use clear_button::*;
|
||||
pub use input::*;
|
||||
|
||||
@@ -47,9 +47,8 @@ mod window_border;
|
||||
///
|
||||
/// This must be called before using any of the UI components.
|
||||
/// You can initialize the UI module at your application's entry point.
|
||||
pub fn init(cx: &mut gpui::AppContext) {
|
||||
pub fn init(cx: &mut gpui::App) {
|
||||
theme::init(cx);
|
||||
dock_area::init(cx);
|
||||
dropdown::init(cx);
|
||||
input::init(cx);
|
||||
list::init(cx);
|
||||
|
||||
@@ -5,18 +5,19 @@ use crate::{
|
||||
v_flex, Icon, IconName, Size,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, AppContext, Entity,
|
||||
FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyBinding, Length,
|
||||
actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
|
||||
Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length,
|
||||
ListSizingBehavior, MouseButton, ParentElement, Render, ScrollStrategy, SharedString, Styled,
|
||||
Task, UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
|
||||
Task, UniformListScrollHandle, Window,
|
||||
};
|
||||
use smol::Timer;
|
||||
use std::{cell::Cell, rc::Rc, time::Duration};
|
||||
|
||||
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
let context: Option<&str> = Some("List");
|
||||
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("escape", Cancel, context),
|
||||
KeyBinding::new("enter", Confirm, context),
|
||||
@@ -32,20 +33,30 @@ pub trait ListDelegate: Sized + 'static {
|
||||
|
||||
/// When Query Input change, this method will be called.
|
||||
/// You can perform search here.
|
||||
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) -> Task<()> {
|
||||
fn perform_search(
|
||||
&mut self,
|
||||
query: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<List<Self>>,
|
||||
) -> Task<()> {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
/// Return the number of items in the list.
|
||||
fn items_count(&self, cx: &AppContext) -> usize;
|
||||
fn items_count(&self, cx: &App) -> usize;
|
||||
|
||||
/// Render the item at the given index.
|
||||
///
|
||||
/// Return None will skip the item.
|
||||
fn render_item(&self, ix: usize, cx: &mut ViewContext<List<Self>>) -> Option<Self::Item>;
|
||||
fn render_item(
|
||||
&self,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<List<Self>>,
|
||||
) -> Option<Self::Item>;
|
||||
|
||||
/// Return a Element to show when list is empty.
|
||||
fn render_empty(&self, cx: &mut ViewContext<List<Self>>) -> impl IntoElement {
|
||||
fn render_empty(&self, window: &mut Window, cx: &mut Context<List<Self>>) -> impl IntoElement {
|
||||
div()
|
||||
}
|
||||
|
||||
@@ -56,30 +67,39 @@ pub trait ListDelegate: Sized + 'static {
|
||||
/// For example: The last search results, or the last selected item.
|
||||
///
|
||||
/// Default is None, that means no initial state.
|
||||
fn render_initial(&self, cx: &mut ViewContext<List<Self>>) -> Option<AnyElement> {
|
||||
fn render_initial(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<List<Self>>,
|
||||
) -> Option<AnyElement> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Return the confirmed index of the selected item.
|
||||
fn confirmed_index(&self, cx: &AppContext) -> Option<usize> {
|
||||
fn confirmed_index(&self, cx: &App) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Set the selected index, just store the ix, don't confirm.
|
||||
fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>);
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<List<Self>>,
|
||||
);
|
||||
|
||||
/// Set the confirm and give the selected index, this is means user have clicked the item or pressed Enter.
|
||||
fn confirm(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {}
|
||||
fn confirm(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<List<Self>>) {}
|
||||
|
||||
/// Cancel the selection, e.g.: Pressed ESC.
|
||||
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {}
|
||||
fn cancel(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {}
|
||||
}
|
||||
|
||||
pub struct List<D: ListDelegate> {
|
||||
focus_handle: FocusHandle,
|
||||
delegate: D,
|
||||
max_height: Option<Length>,
|
||||
query_input: Option<View<TextInput>>,
|
||||
query_input: Option<Entity<TextInput>>,
|
||||
last_query: Option<String>,
|
||||
loading: bool,
|
||||
|
||||
@@ -97,11 +117,11 @@ impl<D> List<D>
|
||||
where
|
||||
D: ListDelegate,
|
||||
{
|
||||
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
|
||||
let query_input = cx.new_view(|cx| {
|
||||
TextInput::new(cx)
|
||||
pub fn new(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let query_input = cx.new(|cx| {
|
||||
TextInput::new(window, cx)
|
||||
.appearance(false)
|
||||
.prefix(|cx| {
|
||||
.prefix(|_window, cx| {
|
||||
Icon::new(IconName::Search)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
})
|
||||
@@ -109,7 +129,7 @@ where
|
||||
.cleanable()
|
||||
});
|
||||
|
||||
cx.subscribe(&query_input, Self::on_query_input_event)
|
||||
cx.subscribe_in(&query_input, window, Self::on_query_input_event)
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
@@ -130,10 +150,10 @@ where
|
||||
}
|
||||
|
||||
/// Set the size
|
||||
pub fn set_size(&mut self, size: Size, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_size(&mut self, size: Size, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(input) = &self.query_input {
|
||||
input.update(cx, |input, cx| {
|
||||
input.set_size(size, cx);
|
||||
input.set_size(size, window, cx);
|
||||
})
|
||||
}
|
||||
self.size = size;
|
||||
@@ -154,8 +174,13 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_query_input(&mut self, query_input: View<TextInput>, cx: &mut ViewContext<Self>) {
|
||||
cx.subscribe(&query_input, Self::on_query_input_event)
|
||||
pub fn set_query_input(
|
||||
&mut self,
|
||||
query_input: Entity<TextInput>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.subscribe_in(&query_input, window, Self::on_query_input_event)
|
||||
.detach();
|
||||
self.query_input = Some(query_input);
|
||||
}
|
||||
@@ -168,13 +193,18 @@ where
|
||||
&mut self.delegate
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, cx: &mut WindowContext) {
|
||||
self.focus_handle(cx).focus(cx);
|
||||
pub fn focus(&mut self, window: &mut Window, cx: &mut App) {
|
||||
self.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
pub fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_selected_index(
|
||||
&mut self,
|
||||
ix: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
self.delegate.set_selected_index(ix, cx);
|
||||
self.delegate.set_selected_index(ix, window, cx);
|
||||
}
|
||||
|
||||
pub fn selected_index(&self) -> Option<usize> {
|
||||
@@ -182,31 +212,35 @@ where
|
||||
}
|
||||
|
||||
/// Set the query_input text
|
||||
pub fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_query(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(query_input) = &self.query_input {
|
||||
let query = query.to_owned();
|
||||
query_input.update(cx, |input, cx| input.set_text(query, cx))
|
||||
query_input.update(cx, |input, cx| input.set_text(query, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the query_input text
|
||||
pub fn query(&self, cx: &mut ViewContext<Self>) -> Option<SharedString> {
|
||||
pub fn query(&self, _window: &mut Window, cx: &mut Context<Self>) -> Option<SharedString> {
|
||||
self.query_input.as_ref().map(|input| input.read(cx).text())
|
||||
}
|
||||
|
||||
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
||||
fn render_scrollbar(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if !self.enable_scrollbar {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Scrollbar::uniform_scroll(
|
||||
cx.view().entity_id(),
|
||||
cx.model().entity_id(),
|
||||
self.scrollbar_state.clone(),
|
||||
self.vertical_scroll_handle.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn scroll_to_selected_item(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
fn scroll_to_selected_item(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
self.vertical_scroll_handle
|
||||
.scroll_to_item(ix, ScrollStrategy::Top);
|
||||
@@ -215,9 +249,10 @@ where
|
||||
|
||||
fn on_query_input_event(
|
||||
&mut self,
|
||||
_: View<TextInput>,
|
||||
_: &Entity<TextInput>,
|
||||
event: &InputEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
InputEvent::Change(text) => {
|
||||
@@ -226,13 +261,13 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
self.set_loading(true, cx);
|
||||
let search = self.delegate.perform_search(&text, cx);
|
||||
self.set_loading(true, window, cx);
|
||||
let search = self.delegate.perform_search(&text, window, cx);
|
||||
|
||||
self._search_task = cx.spawn(|this, mut cx| async move {
|
||||
self._search_task = cx.spawn_in(window, |this, mut window| async move {
|
||||
search.await;
|
||||
|
||||
let _ = this.update(&mut cx, |this, _| {
|
||||
_ = this.update_in(&mut window, |this, _, _| {
|
||||
this.vertical_scroll_handle
|
||||
.scroll_to_item(0, ScrollStrategy::Top);
|
||||
this.last_query = Some(text);
|
||||
@@ -240,40 +275,45 @@ where
|
||||
|
||||
// Always wait 100ms to avoid flicker
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
let _ = this.update(&mut cx, |this, cx| {
|
||||
this.set_loading(false, cx);
|
||||
_ = this.update_in(&mut window, |this, window, cx| {
|
||||
this.set_loading(false, window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
InputEvent::PressEnter => self.on_action_confirm(&Confirm, cx),
|
||||
InputEvent::PressEnter => self.on_action_confirm(&Confirm, window, cx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) {
|
||||
fn set_loading(&mut self, loading: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.loading = loading;
|
||||
if let Some(input) = &self.query_input {
|
||||
input.update(cx, |input, cx| input.set_loading(loading, cx))
|
||||
input.update(cx, |input, cx| input.set_loading(loading, window, cx))
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_action_cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
self.set_selected_index(None, cx);
|
||||
self.delegate.cancel(cx);
|
||||
fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.set_selected_index(None, window, cx);
|
||||
self.delegate.cancel(window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_action_confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
fn on_action_confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.delegate.items_count(cx) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.delegate.confirm(self.selected_index, cx);
|
||||
self.delegate.confirm(self.selected_index, window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_action_select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
fn on_action_select_prev(
|
||||
&mut self,
|
||||
_: &SelectPrev,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.delegate.items_count(cx) == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -285,12 +325,18 @@ where
|
||||
self.selected_index = Some(self.delegate.items_count(cx) - 1);
|
||||
}
|
||||
|
||||
self.delegate.set_selected_index(self.selected_index, cx);
|
||||
self.scroll_to_selected_item(cx);
|
||||
self.delegate
|
||||
.set_selected_index(self.selected_index, window, cx);
|
||||
self.scroll_to_selected_item(window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_action_select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
fn on_action_select_next(
|
||||
&mut self,
|
||||
_: &SelectNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.delegate.items_count(cx) == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -305,17 +351,23 @@ where
|
||||
self.selected_index = Some(0);
|
||||
}
|
||||
|
||||
self.delegate.set_selected_index(self.selected_index, cx);
|
||||
self.scroll_to_selected_item(cx);
|
||||
self.delegate
|
||||
.set_selected_index(self.selected_index, window, cx);
|
||||
self.scroll_to_selected_item(window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_list_item(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_list_item(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
div()
|
||||
.id("list-item")
|
||||
.w_full()
|
||||
.relative()
|
||||
.children(self.delegate.render_item(ix, cx))
|
||||
.children(self.delegate.render_item(ix, window, cx))
|
||||
.when_some(self.selected_index, |this, selected_index| {
|
||||
this.when(ix == selected_index, |this| {
|
||||
this.child(
|
||||
@@ -345,15 +397,15 @@ where
|
||||
})
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, cx| {
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.right_clicked_index = None;
|
||||
this.selected_index = Some(ix);
|
||||
this.on_action_confirm(&Confirm, cx);
|
||||
this.on_action_confirm(&Confirm, window, cx);
|
||||
}),
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Right,
|
||||
cx.listener(move |this, _, cx| {
|
||||
cx.listener(move |this, _, _window, cx| {
|
||||
this.right_clicked_index = Some(ix);
|
||||
cx.notify();
|
||||
}),
|
||||
@@ -361,11 +413,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> FocusableView for List<D>
|
||||
impl<D> Focusable for List<D>
|
||||
where
|
||||
D: ListDelegate,
|
||||
{
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
if let Some(query_input) = &self.query_input {
|
||||
query_input.focus_handle(cx)
|
||||
} else {
|
||||
@@ -378,8 +430,8 @@ impl<D> Render for List<D>
|
||||
where
|
||||
D: ListDelegate,
|
||||
{
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
let vertical_scroll_handle = self.vertical_scroll_handle.clone();
|
||||
let items_count = self.delegate.items_count(cx);
|
||||
let sizing_behavior = if self.max_height.is_some() {
|
||||
@@ -390,7 +442,7 @@ where
|
||||
|
||||
let initial_view = if let Some(input) = &self.query_input {
|
||||
if input.read(cx).text().is_empty() {
|
||||
self.delegate().render_initial(cx)
|
||||
self.delegate().render_initial(window, cx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -432,14 +484,14 @@ where
|
||||
.when_some(self.max_height, |this, h| this.max_h(h))
|
||||
.overflow_hidden()
|
||||
.when(items_count == 0, |this| {
|
||||
this.child(self.delegate().render_empty(cx))
|
||||
this.child(self.delegate().render_empty(window, cx))
|
||||
})
|
||||
.when(items_count > 0, |this| {
|
||||
this.child(
|
||||
uniform_list(view, "uniform-list", items_count, {
|
||||
move |list, visible_range, cx| {
|
||||
move |list, visible_range, window, cx| {
|
||||
visible_range
|
||||
.map(|ix| list.render_list_item(ix, cx))
|
||||
.map(|ix| list.render_list_item(ix, window, cx))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
})
|
||||
@@ -449,13 +501,13 @@ where
|
||||
.into_any_element(),
|
||||
)
|
||||
})
|
||||
.children(self.render_scrollbar(cx)),
|
||||
.children(self.render_scrollbar(window, cx)),
|
||||
)
|
||||
}
|
||||
})
|
||||
// Click out to cancel right clicked row
|
||||
.when(self.right_clicked_index.is_some(), |this| {
|
||||
this.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
this.on_mouse_down_out(cx.listener(|this, _, _window, cx| {
|
||||
this.right_clicked_index = None;
|
||||
cx.notify();
|
||||
}))
|
||||
|
||||
@@ -4,15 +4,15 @@ use crate::{
|
||||
Disableable, Icon, IconName, Selectable, Sizable as _,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement,
|
||||
IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce, Stateful,
|
||||
StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
div, prelude::FluentBuilder as _, AnyElement, App, ClickEvent, Div, ElementId,
|
||||
InteractiveElement, IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce,
|
||||
Stateful, StatefulInteractiveElement as _, Styled, Window,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>;
|
||||
type OnMouseEnter = Option<Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>>;
|
||||
type Suffix = Option<Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>>;
|
||||
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>;
|
||||
type OnMouseEnter = Option<Box<dyn Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static>>;
|
||||
type Suffix = Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ListItem {
|
||||
@@ -71,21 +71,26 @@ impl ListItem {
|
||||
/// Set the suffix element of the input field, for example a clear button.
|
||||
pub fn suffix<F, E>(mut self, builder: F) -> Self
|
||||
where
|
||||
F: Fn(&mut WindowContext) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut App) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.suffix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_mouse_enter(
|
||||
mut self,
|
||||
handler: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
|
||||
handler: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_mouse_enter = Some(Box::new(handler));
|
||||
self
|
||||
@@ -123,7 +128,7 @@ impl ParentElement for ListItem {
|
||||
}
|
||||
|
||||
impl RenderOnce for ListItem {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let is_active = self.selected || self.confirmed;
|
||||
|
||||
self.base
|
||||
@@ -134,7 +139,7 @@ impl RenderOnce for ListItem {
|
||||
.when_some(self.on_click, |this, on_click| {
|
||||
if !self.disabled {
|
||||
this.cursor_pointer()
|
||||
.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||
.on_mouse_down(MouseButton::Left, move |_, _window, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_click(on_click)
|
||||
@@ -151,7 +156,7 @@ impl RenderOnce for ListItem {
|
||||
// Mouse enter
|
||||
.when_some(self.on_mouse_enter, |this, on_mouse_enter| {
|
||||
if !self.disabled {
|
||||
this.on_mouse_move(move |ev, cx| (on_mouse_enter)(ev, cx))
|
||||
this.on_mouse_move(move |ev, window, cx| (on_mouse_enter)(ev, window, cx))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
@@ -176,6 +181,6 @@ impl RenderOnce for ListItem {
|
||||
))
|
||||
}),
|
||||
)
|
||||
.when_some(self.suffix, |this, suffix| this.child(suffix(cx)))
|
||||
.when_some(self.suffix, |this, suffix| this.child(suffix(window, cx)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::{
|
||||
};
|
||||
use gpui::{
|
||||
actions, anchored, div, point, prelude::FluentBuilder, px, relative, Animation,
|
||||
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle,
|
||||
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
||||
RenderOnce, SharedString, Styled, WindowContext,
|
||||
AnimationExt as _, AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, InteractiveElement,
|
||||
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
|
||||
Styled, Window,
|
||||
};
|
||||
use std::{rc::Rc, time::Duration};
|
||||
|
||||
@@ -16,12 +16,10 @@ actions!(modal, [Escape]);
|
||||
|
||||
const CONTEXT: &str = "Modal";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
||||
}
|
||||
|
||||
type OnClose = Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Modal {
|
||||
base: Div,
|
||||
@@ -31,7 +29,7 @@ pub struct Modal {
|
||||
width: Pixels,
|
||||
max_width: Option<Pixels>,
|
||||
margin_top: Option<Pixels>,
|
||||
on_close: OnClose,
|
||||
on_close: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
||||
show_close: bool,
|
||||
keyboard: bool,
|
||||
/// This will be change when open the modal, the focus handle is create when open the modal.
|
||||
@@ -41,7 +39,7 @@ pub struct Modal {
|
||||
}
|
||||
|
||||
impl Modal {
|
||||
pub fn new(cx: &mut WindowContext) -> Self {
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Self {
|
||||
let base = v_flex()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
@@ -62,7 +60,7 @@ impl Modal {
|
||||
overlay: true,
|
||||
keyboard: true,
|
||||
layer_ix: 0,
|
||||
on_close: Rc::new(|_, _| {}),
|
||||
on_close: Rc::new(|_, _, _| {}),
|
||||
show_close: true,
|
||||
}
|
||||
}
|
||||
@@ -82,7 +80,7 @@ impl Modal {
|
||||
/// Sets the callback for when the modal is closed.
|
||||
pub fn on_close(
|
||||
mut self,
|
||||
on_close: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_close = Rc::new(on_close);
|
||||
self
|
||||
@@ -142,11 +140,11 @@ impl Styled for Modal {
|
||||
}
|
||||
|
||||
impl RenderOnce for Modal {
|
||||
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl gpui::IntoElement {
|
||||
let layer_ix = self.layer_ix;
|
||||
let on_close = self.on_close.clone();
|
||||
let window_paddings = crate::window_border::window_paddings(cx);
|
||||
let view_size = cx.viewport_size()
|
||||
let window_paddings = crate::window_border::window_paddings(window, cx);
|
||||
let view_size = window.viewport_size()
|
||||
- gpui::size(
|
||||
window_paddings.left + window_paddings.right,
|
||||
window_paddings.top + window_paddings.bottom,
|
||||
@@ -172,9 +170,9 @@ impl RenderOnce for Modal {
|
||||
})
|
||||
.on_mouse_down(MouseButton::Left, {
|
||||
let on_close = self.on_close.clone();
|
||||
move |_, cx| {
|
||||
on_close(&ClickEvent::default(), cx);
|
||||
cx.close_modal();
|
||||
move |_, window, cx| {
|
||||
on_close(&ClickEvent::default(), window, cx);
|
||||
window.close_modal(cx);
|
||||
}
|
||||
})
|
||||
.child(
|
||||
@@ -185,13 +183,13 @@ impl RenderOnce for Modal {
|
||||
.when(self.keyboard, |this| {
|
||||
this.on_action({
|
||||
let on_close = self.on_close.clone();
|
||||
move |_: &Escape, cx| {
|
||||
move |_: &Escape, window, cx| {
|
||||
// FIXME:
|
||||
//
|
||||
// Here some Modal have no focus_handle, so it will not work will Escape key.
|
||||
// But by now, we `cx.close_modal()` going to close the last active model, so the Escape is unexpected to work.
|
||||
on_close(&ClickEvent::default(), cx);
|
||||
cx.close_modal();
|
||||
on_close(&ClickEvent::default(), window, cx);
|
||||
window.close_modal(cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -227,9 +225,9 @@ impl RenderOnce for Modal {
|
||||
.ghost()
|
||||
.icon(IconName::Close)
|
||||
.on_click(
|
||||
move |_, cx| {
|
||||
on_close(&ClickEvent::default(), cx);
|
||||
cx.close_modal();
|
||||
move |_, window, cx| {
|
||||
on_close(&ClickEvent::default(), window, cx);
|
||||
window.close_modal(cx);
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::{
|
||||
v_flex, Icon, IconName, Sizable as _, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder, px, Animation, AnimationExt, ClickEvent, DismissEvent, ElementId,
|
||||
EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext,
|
||||
div, prelude::FluentBuilder, px, Animation, AnimationExt, App, AppContext, ClickEvent, Context,
|
||||
DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use smol::Timer;
|
||||
use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration};
|
||||
@@ -42,7 +42,7 @@ impl From<(TypeId, ElementId)> for NotificationId {
|
||||
}
|
||||
}
|
||||
|
||||
type OnClick = Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext)>>;
|
||||
type OnClick = Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>;
|
||||
|
||||
/// A notification element.
|
||||
pub struct Notification {
|
||||
@@ -175,18 +175,18 @@ impl Notification {
|
||||
/// Set the click callback of the notification.
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Arc::new(on_click));
|
||||
self
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
fn dismiss(&mut self, _: &ClickEvent, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.closing = true;
|
||||
cx.notify();
|
||||
|
||||
// Dismiss the notification after 0.15s to show the animation.
|
||||
cx.spawn(|view, mut cx| async move {
|
||||
cx.spawn(|view, cx| async move {
|
||||
Timer::after(Duration::from_secs_f32(0.15)).await;
|
||||
cx.update(|cx| {
|
||||
if let Some(view) = view.upgrade() {
|
||||
@@ -206,7 +206,7 @@ impl EventEmitter<DismissEvent> for Notification {}
|
||||
impl FluentBuilder for Notification {}
|
||||
|
||||
impl Render for Notification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let closing = self.closing;
|
||||
let icon = match self.icon.clone() {
|
||||
Some(icon) => icon,
|
||||
@@ -250,9 +250,9 @@ impl Render for Notification {
|
||||
)
|
||||
.when_some(self.on_click.clone(), |this, on_click| {
|
||||
this.cursor_pointer()
|
||||
.on_click(cx.listener(move |view, event, cx| {
|
||||
view.dismiss(event, cx);
|
||||
on_click(event, cx);
|
||||
.on_click(cx.listener(move |view, event, window, cx| {
|
||||
view.dismiss(event, window, cx);
|
||||
on_click(event, window, cx);
|
||||
}))
|
||||
})
|
||||
.when(!self.autohide, |this| {
|
||||
@@ -292,19 +292,24 @@ impl Render for Notification {
|
||||
/// A list of notifications.
|
||||
pub struct NotificationList {
|
||||
/// Notifications that will be auto hidden.
|
||||
pub(crate) notifications: VecDeque<View<Notification>>,
|
||||
pub(crate) notifications: VecDeque<Entity<Notification>>,
|
||||
expanded: bool,
|
||||
}
|
||||
|
||||
impl NotificationList {
|
||||
pub fn new(_cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
notifications: VecDeque::new(),
|
||||
expanded: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, notification: impl Into<Notification>, cx: &mut ViewContext<Self>) {
|
||||
pub fn push(
|
||||
&mut self,
|
||||
notification: impl Into<Notification>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let notification = notification.into();
|
||||
let id = notification.id.clone();
|
||||
let autohide = notification.autohide;
|
||||
@@ -312,7 +317,8 @@ impl NotificationList {
|
||||
// Remove the notification by id, for keep unique.
|
||||
self.notifications.retain(|note| note.read(cx).id != id);
|
||||
|
||||
let notification = cx.new_view(|_| notification);
|
||||
let notification = cx.new(|_| notification);
|
||||
|
||||
cx.subscribe(¬ification, move |view, _, _: &DismissEvent, cx| {
|
||||
view.notifications.retain(|note| id != note.read(cx).id);
|
||||
})
|
||||
@@ -320,13 +326,13 @@ impl NotificationList {
|
||||
|
||||
self.notifications.push_back(notification.clone());
|
||||
if autohide {
|
||||
// Sleep for 5 seconds to autohide the notification
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
Timer::after(Duration::from_secs(5)).await;
|
||||
// Sleep for 3 seconds to autohide the notification
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
Timer::after(Duration::from_secs(3)).await;
|
||||
|
||||
if let Err(err) = notification
|
||||
.update(&mut cx, |note, cx| note.dismiss(&ClickEvent::default(), cx))
|
||||
{
|
||||
if let Err(err) = notification.update_in(&mut cx, |note, window, cx| {
|
||||
note.dismiss(&ClickEvent::default(), window, cx)
|
||||
}) {
|
||||
println!("failed to auto hide notification: {:?}", err);
|
||||
}
|
||||
})
|
||||
@@ -335,19 +341,23 @@ impl NotificationList {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn clear(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.notifications.clear();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn notifications(&self) -> Vec<View<Notification>> {
|
||||
pub fn notifications(&self) -> Vec<Entity<Notification>> {
|
||||
self.notifications.iter().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for NotificationList {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let size = cx.viewport_size();
|
||||
fn render(
|
||||
&mut self,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let size = window.viewport_size();
|
||||
let items = self.notifications.iter().rev().take(10).rev().cloned();
|
||||
|
||||
div()
|
||||
@@ -364,9 +374,9 @@ impl Render for NotificationList {
|
||||
.relative()
|
||||
.right_0()
|
||||
.h(size.height - px(8.))
|
||||
.on_hover(cx.listener(|view, hovered, cx| {
|
||||
.on_hover(cx.listener(|view, hovered, _window, cx| {
|
||||
view.expanded = *hovered;
|
||||
cx.notify()
|
||||
cx.notify();
|
||||
}))
|
||||
.gap_3()
|
||||
.children(items),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::{Selectable, StyledExt as _};
|
||||
use gpui::{
|
||||
actions, anchored, deferred, div, prelude::FluentBuilder as _, px, AnyElement, AppContext,
|
||||
Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId, EventEmitter, FocusHandle,
|
||||
FocusableView, GlobalElementId, Hitbox, InteractiveElement as _, IntoElement, KeyBinding,
|
||||
LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render,
|
||||
Style, StyleRefinement, Styled, View, ViewContext, VisualContext, WindowContext,
|
||||
actions, anchored, deferred, div, prelude::FluentBuilder as _, px, AnyElement, App, Bounds,
|
||||
Context, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, GlobalElementId, Hitbox, InteractiveElement as _, IntoElement,
|
||||
KeyBinding, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
|
||||
Render, Style, StyleRefinement, Styled, Window,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
@@ -12,22 +12,20 @@ const CONTEXT: &str = "Popover";
|
||||
|
||||
actions!(popover, [Escape]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
||||
}
|
||||
|
||||
type Content<T> = Rc<dyn Fn(&mut ViewContext<T>) -> AnyElement>;
|
||||
|
||||
pub struct PopoverContent {
|
||||
focus_handle: FocusHandle,
|
||||
content: Content<Self>,
|
||||
content: Rc<dyn Fn(&mut Window, &mut Context<Self>) -> AnyElement>,
|
||||
max_width: Option<Pixels>,
|
||||
}
|
||||
|
||||
impl PopoverContent {
|
||||
pub fn new<B>(cx: &mut WindowContext, content: B) -> Self
|
||||
pub fn new<B>(_window: &mut Window, cx: &mut App, content: B) -> Self
|
||||
where
|
||||
B: Fn(&mut ViewContext<Self>) -> AnyElement + 'static,
|
||||
B: Fn(&mut Window, &mut Context<Self>) -> AnyElement + 'static,
|
||||
{
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
@@ -46,32 +44,29 @@ impl PopoverContent {
|
||||
|
||||
impl EventEmitter<DismissEvent> for PopoverContent {}
|
||||
|
||||
impl FocusableView for PopoverContent {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for PopoverContent {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for PopoverContent {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.key_context(CONTEXT)
|
||||
.on_action(cx.listener(|_, _: &Escape, cx| cx.emit(DismissEvent)))
|
||||
.on_action(cx.listener(|_, _: &Escape, _, cx| cx.emit(DismissEvent)))
|
||||
.p_2()
|
||||
.when_some(self.max_width, |this, v| this.max_w(v))
|
||||
.child(self.content.clone()(cx))
|
||||
.child(self.content.clone()(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
type Trigger = Option<Box<dyn FnOnce(bool, &WindowContext) -> AnyElement + 'static>>;
|
||||
type ViewContent<M> = Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>;
|
||||
|
||||
pub struct Popover<M: ManagedView> {
|
||||
id: ElementId,
|
||||
anchor: Corner,
|
||||
trigger: Trigger,
|
||||
content: ViewContent<M>,
|
||||
trigger: Option<Box<dyn FnOnce(bool, &Window, &App) -> AnyElement + 'static>>,
|
||||
content: Option<Rc<dyn Fn(&mut Window, &mut App) -> Entity<M> + 'static>>,
|
||||
/// Style for trigger element.
|
||||
/// This is used for hotfix the trigger element style to support w_full.
|
||||
trigger_style: Option<StyleRefinement>,
|
||||
@@ -111,7 +106,7 @@ where
|
||||
where
|
||||
T: Selectable + IntoElement + 'static,
|
||||
{
|
||||
self.trigger = Some(Box::new(|is_open, _| {
|
||||
self.trigger = Some(Box::new(|is_open, _, _| {
|
||||
trigger.selected(is_open).into_any_element()
|
||||
}));
|
||||
self
|
||||
@@ -121,13 +116,12 @@ where
|
||||
self.trigger_style = Some(style);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the content of the popover.
|
||||
///
|
||||
/// The `content` is a closure that returns an `AnyElement`.
|
||||
pub fn content<C>(mut self, content: C) -> Self
|
||||
where
|
||||
C: Fn(&mut WindowContext) -> View<M> + 'static,
|
||||
C: Fn(&mut Window, &mut App) -> Entity<M> + 'static,
|
||||
{
|
||||
self.content = Some(Rc::new(content));
|
||||
self
|
||||
@@ -144,12 +138,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
fn render_trigger(&mut self, is_open: bool, cx: &mut WindowContext) -> AnyElement {
|
||||
fn render_trigger(&mut self, is_open: bool, window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
let Some(trigger) = self.trigger.take() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
(trigger)(is_open, cx)
|
||||
(trigger)(is_open, window, cx)
|
||||
}
|
||||
|
||||
fn resolved_corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||
@@ -164,14 +158,15 @@ where
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
id: &GlobalElementId,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut Self, &mut PopoverElementState<M>, &mut WindowContext) -> R,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
f: impl FnOnce(&mut Self, &mut PopoverElementState<M>, &mut Window, &mut App) -> R,
|
||||
) -> R {
|
||||
cx.with_optional_element_state::<PopoverElementState<M>, _>(
|
||||
window.with_optional_element_state::<PopoverElementState<M>, _>(
|
||||
Some(id),
|
||||
|element_state, cx| {
|
||||
|element_state, window| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, cx);
|
||||
let result = f(self, &mut element_state, window, cx);
|
||||
(result, Some(element_state))
|
||||
},
|
||||
)
|
||||
@@ -194,7 +189,7 @@ pub struct PopoverElementState<M> {
|
||||
popover_layout_id: Option<LayoutId>,
|
||||
popover_element: Option<AnyElement>,
|
||||
trigger_element: Option<AnyElement>,
|
||||
content_view: Rc<RefCell<Option<View<M>>>>,
|
||||
content_view: Rc<RefCell<Option<Entity<M>>>>,
|
||||
/// Trigger bounds for positioning the popover.
|
||||
trigger_bounds: Option<Bounds<Pixels>>,
|
||||
}
|
||||
@@ -229,7 +224,8 @@ impl<M: ManagedView> Element for Popover<M> {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
|
||||
@@ -246,73 +242,81 @@ impl<M: ManagedView> Element for Popover<M> {
|
||||
}
|
||||
}
|
||||
|
||||
self.with_element_state(id.unwrap(), cx, |view, element_state, cx| {
|
||||
let mut popover_layout_id = None;
|
||||
let mut popover_element = None;
|
||||
let mut is_open = false;
|
||||
self.with_element_state(
|
||||
id.unwrap(),
|
||||
window,
|
||||
cx,
|
||||
|view, element_state, window, cx| {
|
||||
let mut popover_layout_id = None;
|
||||
let mut popover_element = None;
|
||||
let mut is_open = false;
|
||||
|
||||
if let Some(content_view) = element_state.content_view.borrow_mut().as_mut() {
|
||||
is_open = true;
|
||||
if let Some(content_view) = element_state.content_view.borrow_mut().as_mut() {
|
||||
is_open = true;
|
||||
|
||||
let mut anchored = anchored()
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(view.anchor);
|
||||
if let Some(trigger_bounds) = element_state.trigger_bounds {
|
||||
anchored = anchored.position(view.resolved_corner(trigger_bounds));
|
||||
let mut anchored = anchored()
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(view.anchor);
|
||||
if let Some(trigger_bounds) = element_state.trigger_bounds {
|
||||
anchored = anchored.position(view.resolved_corner(trigger_bounds));
|
||||
}
|
||||
|
||||
let mut element = {
|
||||
let content_view_mut = element_state.content_view.clone();
|
||||
let anchor = view.anchor;
|
||||
let no_style = view.no_style;
|
||||
deferred(
|
||||
anchored.child(
|
||||
div()
|
||||
.size_full()
|
||||
.occlude()
|
||||
.when(!no_style, |this| this.popover_style(cx))
|
||||
.map(|this| match anchor {
|
||||
Corner::TopLeft | Corner::TopRight => this.top_1p5(),
|
||||
Corner::BottomLeft | Corner::BottomRight => {
|
||||
this.bottom_1p5()
|
||||
}
|
||||
})
|
||||
.child(content_view.clone())
|
||||
.when(!no_style, |this| {
|
||||
this.on_mouse_down_out(move |_, window, _| {
|
||||
// Update the element_state.content_view to `None`,
|
||||
// so that the `paint`` method will not paint it.
|
||||
*content_view_mut.borrow_mut() = None;
|
||||
window.refresh();
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any()
|
||||
};
|
||||
|
||||
popover_layout_id = Some(element.request_layout(window, cx));
|
||||
popover_element = Some(element);
|
||||
}
|
||||
|
||||
let mut element = {
|
||||
let content_view_mut = element_state.content_view.clone();
|
||||
let anchor = view.anchor;
|
||||
let no_style = view.no_style;
|
||||
deferred(
|
||||
anchored.child(
|
||||
div()
|
||||
.size_full()
|
||||
.occlude()
|
||||
.when(!no_style, |this| this.popover_style(cx))
|
||||
.map(|this| match anchor {
|
||||
Corner::TopLeft | Corner::TopRight => this.top_1p5(),
|
||||
Corner::BottomLeft | Corner::BottomRight => this.bottom_1p5(),
|
||||
})
|
||||
.child(content_view.clone())
|
||||
.when(!no_style, |this| {
|
||||
this.on_mouse_down_out(move |_, cx| {
|
||||
// Update the element_state.content_view to `None`,
|
||||
// so that the `paint`` method will not paint it.
|
||||
*content_view_mut.borrow_mut() = None;
|
||||
cx.refresh();
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any()
|
||||
};
|
||||
let mut trigger_element = view.render_trigger(is_open, window, cx);
|
||||
let trigger_layout_id = trigger_element.request_layout(window, cx);
|
||||
|
||||
popover_layout_id = Some(element.request_layout(cx));
|
||||
popover_element = Some(element);
|
||||
}
|
||||
let layout_id = window.request_layout(
|
||||
style,
|
||||
Some(trigger_layout_id).into_iter().chain(popover_layout_id),
|
||||
cx,
|
||||
);
|
||||
|
||||
let mut trigger_element = view.render_trigger(is_open, cx);
|
||||
let trigger_layout_id = trigger_element.request_layout(cx);
|
||||
|
||||
let layout_id = cx.request_layout(
|
||||
style,
|
||||
Some(trigger_layout_id).into_iter().chain(popover_layout_id),
|
||||
);
|
||||
|
||||
(
|
||||
layout_id,
|
||||
PopoverElementState {
|
||||
trigger_layout_id: Some(trigger_layout_id),
|
||||
popover_layout_id,
|
||||
popover_element,
|
||||
trigger_element: Some(trigger_element),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
(
|
||||
layout_id,
|
||||
PopoverElementState {
|
||||
trigger_layout_id: Some(trigger_layout_id),
|
||||
popover_layout_id,
|
||||
popover_element,
|
||||
trigger_element: Some(trigger_element),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -320,25 +324,26 @@ impl<M: ManagedView> Element for Popover<M> {
|
||||
_id: Option<&gpui::GlobalElementId>,
|
||||
_bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
if let Some(element) = &mut request_layout.trigger_element {
|
||||
element.prepaint(cx);
|
||||
element.prepaint(window, cx);
|
||||
}
|
||||
if let Some(element) = &mut request_layout.popover_element {
|
||||
element.prepaint(cx);
|
||||
element.prepaint(window, cx);
|
||||
}
|
||||
|
||||
let trigger_bounds = request_layout
|
||||
.trigger_layout_id
|
||||
.map(|id| cx.layout_bounds(id));
|
||||
.map(|id| window.layout_bounds(id));
|
||||
|
||||
// Prepare the popover, for get the bounds of it for open window size.
|
||||
let _ = request_layout
|
||||
.popover_layout_id
|
||||
.map(|id| cx.layout_bounds(id));
|
||||
.map(|id| window.layout_bounds(id));
|
||||
|
||||
let hitbox = cx.insert_hitbox(trigger_bounds.unwrap_or_default(), false);
|
||||
let hitbox = window.insert_hitbox(trigger_bounds.unwrap_or_default(), false);
|
||||
|
||||
PrepaintState {
|
||||
trigger_bounds,
|
||||
@@ -352,57 +357,70 @@ impl<M: ManagedView> Element for Popover<M> {
|
||||
_bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.with_element_state(id.unwrap(), cx, |this, element_state, cx| {
|
||||
element_state.trigger_bounds = prepaint.trigger_bounds;
|
||||
self.with_element_state(
|
||||
id.unwrap(),
|
||||
window,
|
||||
cx,
|
||||
|this, element_state, window, cx| {
|
||||
element_state.trigger_bounds = prepaint.trigger_bounds;
|
||||
|
||||
if let Some(mut element) = request_layout.trigger_element.take() {
|
||||
element.paint(cx);
|
||||
}
|
||||
|
||||
if let Some(mut element) = request_layout.popover_element.take() {
|
||||
element.paint(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
// When mouse click down in the trigger bounds, open the popover.
|
||||
let Some(content_build) = this.content.take() else {
|
||||
return;
|
||||
};
|
||||
let old_content_view = element_state.content_view.clone();
|
||||
let hitbox_id = prepaint.hitbox.id;
|
||||
let mouse_button = this.mouse_button;
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == mouse_button
|
||||
&& hitbox_id.is_hovered(cx)
|
||||
{
|
||||
cx.stop_propagation();
|
||||
cx.prevent_default();
|
||||
|
||||
let new_content_view = (content_build)(cx);
|
||||
let old_content_view1 = old_content_view.clone();
|
||||
|
||||
let previous_focus_handle = cx.focused();
|
||||
|
||||
cx.subscribe(&new_content_view, move |modal, _: &DismissEvent, cx| {
|
||||
if modal.focus_handle(cx).contains_focused(cx) {
|
||||
if let Some(previous_focus_handle) = previous_focus_handle.as_ref() {
|
||||
cx.focus(previous_focus_handle);
|
||||
}
|
||||
}
|
||||
*old_content_view1.borrow_mut() = None;
|
||||
|
||||
cx.refresh();
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.focus_view(&new_content_view);
|
||||
*old_content_view.borrow_mut() = Some(new_content_view);
|
||||
cx.refresh();
|
||||
if let Some(mut element) = request_layout.trigger_element.take() {
|
||||
element.paint(window, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let Some(mut element) = request_layout.popover_element.take() {
|
||||
element.paint(window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
// When mouse click down in the trigger bounds, open the popover.
|
||||
let Some(content_build) = this.content.take() else {
|
||||
return;
|
||||
};
|
||||
let old_content_view = element_state.content_view.clone();
|
||||
let hitbox_id = prepaint.hitbox.id;
|
||||
let mouse_button = this.mouse_button;
|
||||
window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == mouse_button
|
||||
&& hitbox_id.is_hovered(window)
|
||||
{
|
||||
cx.stop_propagation();
|
||||
window.prevent_default();
|
||||
|
||||
let new_content_view = (content_build)(window, cx);
|
||||
let old_content_view1 = old_content_view.clone();
|
||||
|
||||
let previous_focus_handle = window.focused(cx);
|
||||
|
||||
window
|
||||
.subscribe(
|
||||
&new_content_view,
|
||||
cx,
|
||||
move |modal, _: &DismissEvent, window, cx| {
|
||||
if modal.focus_handle(cx).contains_focused(window, cx) {
|
||||
if let Some(previous_focus_handle) =
|
||||
previous_focus_handle.as_ref()
|
||||
{
|
||||
window.focus(previous_focus_handle);
|
||||
}
|
||||
}
|
||||
*old_content_view1.borrow_mut() = None;
|
||||
|
||||
window.refresh();
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
window.focus(&new_content_view.focus_handle(cx));
|
||||
*old_content_view.borrow_mut() = Some(new_content_view);
|
||||
window.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@ use crate::{
|
||||
v_flex, Icon, IconName, Selectable, Sizable as _, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement,
|
||||
AppContext, Bounds, Corner, DismissEvent, Edges, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, Render,
|
||||
ScrollHandle, SharedString, StatefulInteractiveElement, Styled, View, ViewContext,
|
||||
VisualContext as _, WeakView, WindowContext,
|
||||
actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App,
|
||||
AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels,
|
||||
Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
|
||||
};
|
||||
use std::{cell::Cell, ops::Deref, rc::Rc};
|
||||
|
||||
@@ -20,8 +19,9 @@ actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
|
||||
|
||||
const ITEM_HEIGHT: Pixels = px(26.);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
let context = Some("PopupMenu");
|
||||
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("enter", Confirm, context),
|
||||
KeyBinding::new("escape", Dismiss, context),
|
||||
@@ -34,7 +34,7 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
|
||||
/// Create a popup menu with the given items, anchored to the TopLeft corner
|
||||
fn popup_menu(
|
||||
self,
|
||||
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Popover<PopupMenu> {
|
||||
self.popup_menu_with_anchor(Corner::TopLeft, f)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
|
||||
fn popup_menu_with_anchor(
|
||||
mut self,
|
||||
anchor: impl Into<Corner>,
|
||||
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Popover<PopupMenu> {
|
||||
let style = self.style().clone();
|
||||
let element_id = self.element_id();
|
||||
@@ -53,7 +53,9 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
|
||||
.trigger(self)
|
||||
.trigger_style(style)
|
||||
.anchor(anchor.into())
|
||||
.content(move |cx| PopupMenu::build(cx, |menu, cx| f(menu, cx)))
|
||||
.content(move |window, cx| {
|
||||
PopupMenu::build(window, cx, |menu, window, cx| f(menu, window, cx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,16 +67,16 @@ enum PopupMenuItem {
|
||||
icon: Option<Icon>,
|
||||
label: SharedString,
|
||||
action: Option<Box<dyn Action>>,
|
||||
handler: Rc<dyn Fn(&mut WindowContext)>,
|
||||
handler: Rc<dyn Fn(&mut Window, &mut App)>,
|
||||
},
|
||||
ElementItem {
|
||||
render: Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>,
|
||||
handler: Rc<dyn Fn(&mut WindowContext)>,
|
||||
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
|
||||
handler: Rc<dyn Fn(&mut Window, &mut App)>,
|
||||
},
|
||||
Submenu {
|
||||
icon: Option<Icon>,
|
||||
label: SharedString,
|
||||
menu: View<PopupMenu>,
|
||||
menu: Entity<PopupMenu>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -94,7 +96,7 @@ impl PopupMenuItem {
|
||||
|
||||
pub struct PopupMenu {
|
||||
/// The parent menu of this menu, if this is a submenu
|
||||
parent_menu: Option<WeakView<Self>>,
|
||||
parent_menu: Option<WeakEntity<Self>>,
|
||||
focus_handle: FocusHandle,
|
||||
menu_items: Vec<PopupMenuItem>,
|
||||
has_icon: bool,
|
||||
@@ -114,14 +116,16 @@ pub struct PopupMenu {
|
||||
|
||||
impl PopupMenu {
|
||||
pub fn build(
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(Self, &mut ViewContext<PopupMenu>) -> Self,
|
||||
) -> View<Self> {
|
||||
cx.new_view(|cx| {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
f: impl FnOnce(Self, &mut Window, &mut Context<PopupMenu>) -> Self,
|
||||
) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let _on_blur_subscription = cx.on_blur(&focus_handle, |this: &mut PopupMenu, cx| {
|
||||
this.dismiss(&Dismiss, cx)
|
||||
});
|
||||
let _on_blur_subscription =
|
||||
cx.on_blur(&focus_handle, window, |this: &mut PopupMenu, window, cx| {
|
||||
this.dismiss(&Dismiss, window, cx)
|
||||
});
|
||||
|
||||
let menu = Self {
|
||||
focus_handle,
|
||||
@@ -139,8 +143,8 @@ impl PopupMenu {
|
||||
scroll_state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||
_subscriptions: [_on_blur_subscription],
|
||||
};
|
||||
cx.refresh();
|
||||
f(menu, cx)
|
||||
window.refresh();
|
||||
f(menu, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -183,7 +187,7 @@ impl PopupMenu {
|
||||
icon: None,
|
||||
label: label.into(),
|
||||
action: None,
|
||||
handler: Rc::new(move |cx| cx.open_url(&href)),
|
||||
handler: Rc::new(move |_window, cx| cx.open_url(&href)),
|
||||
});
|
||||
self
|
||||
}
|
||||
@@ -200,7 +204,7 @@ impl PopupMenu {
|
||||
icon: Some(icon.into()),
|
||||
label: label.into(),
|
||||
action: None,
|
||||
handler: Rc::new(move |cx| cx.open_url(&href)),
|
||||
handler: Rc::new(move |_window, cx| cx.open_url(&href)),
|
||||
});
|
||||
self
|
||||
}
|
||||
@@ -235,21 +239,21 @@ impl PopupMenu {
|
||||
/// Add Menu Item with custom element render.
|
||||
pub fn menu_with_element<F, E>(mut self, builder: F, action: Box<dyn Action>) -> Self
|
||||
where
|
||||
F: Fn(&mut WindowContext) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut App) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.menu_items.push(PopupMenuItem::ElementItem {
|
||||
render: Box::new(move |cx| builder(cx).into_any_element()),
|
||||
render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
|
||||
handler: self.wrap_handler(action),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut WindowContext)> {
|
||||
fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut Window, &mut App)> {
|
||||
let action_focus_handle = self.action_focus_handle.clone();
|
||||
|
||||
Rc::new(move |cx| {
|
||||
cx.activate_window();
|
||||
Rc::new(move |window, cx| {
|
||||
window.activate_window();
|
||||
|
||||
// Focus back to the user expected focus handle
|
||||
// Then the actions listened on that focus handle can be received
|
||||
@@ -264,10 +268,10 @@ impl PopupMenu {
|
||||
// If the actions are listened on the `PanelContent`,
|
||||
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
|
||||
if let Some(handle) = action_focus_handle.as_ref() {
|
||||
cx.focus(handle);
|
||||
window.focus(handle);
|
||||
}
|
||||
|
||||
cx.dispatch_action(action.boxed_clone());
|
||||
cx.dispatch_action(action.as_ref());
|
||||
})
|
||||
}
|
||||
|
||||
@@ -307,10 +311,11 @@ impl PopupMenu {
|
||||
pub fn submenu(
|
||||
self,
|
||||
label: impl Into<SharedString>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Self {
|
||||
self.submenu_with_icon(None, label, cx, f)
|
||||
self.submenu_with_icon(None, label, window, cx, f)
|
||||
}
|
||||
|
||||
/// Add a Submenu item with icon
|
||||
@@ -318,11 +323,12 @@ impl PopupMenu {
|
||||
mut self,
|
||||
icon: Option<Icon>,
|
||||
label: impl Into<SharedString>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Self {
|
||||
let submenu = PopupMenu::build(cx, f);
|
||||
let parent_menu = cx.view().downgrade();
|
||||
let submenu = PopupMenu::build(window, cx, f);
|
||||
let parent_menu = cx.model().downgrade();
|
||||
submenu.update(cx, |view, _| {
|
||||
view.parent_menu = Some(parent_menu);
|
||||
});
|
||||
@@ -335,7 +341,7 @@ impl PopupMenu {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn active_submenu(&self) -> Option<View<PopupMenu>> {
|
||||
pub(crate) fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
|
||||
if let Some(ix) = self.hovered_menu_ix {
|
||||
if let Some(item) = self.menu_items.get(ix) {
|
||||
return match item {
|
||||
@@ -359,31 +365,31 @@ impl PopupMenu {
|
||||
.filter(|(_, item)| item.is_clickable())
|
||||
}
|
||||
|
||||
fn on_click(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
fn on_click(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.stop_propagation();
|
||||
cx.prevent_default();
|
||||
window.prevent_default();
|
||||
self.selected_index = Some(ix);
|
||||
self.confirm(&Confirm, cx);
|
||||
self.confirm(&Confirm, window, cx);
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(index) = self.selected_index {
|
||||
let item = self.menu_items.get(index);
|
||||
match item {
|
||||
Some(PopupMenuItem::Item { handler, .. }) => {
|
||||
handler(cx);
|
||||
self.dismiss(&Dismiss, cx)
|
||||
handler(window, cx);
|
||||
self.dismiss(&Dismiss, window, cx)
|
||||
}
|
||||
Some(PopupMenuItem::ElementItem { handler, .. }) => {
|
||||
handler(cx);
|
||||
self.dismiss(&Dismiss, cx)
|
||||
handler(window, cx);
|
||||
self.dismiss(&Dismiss, window, cx)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
fn select_next(&mut self, _: &SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = self.clickable_menu_items().count();
|
||||
if count > 0 {
|
||||
let last_ix = count.saturating_sub(1);
|
||||
@@ -397,7 +403,7 @@ impl PopupMenu {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
fn select_prev(&mut self, _: &SelectPrev, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = self.clickable_menu_items().count();
|
||||
if count > 0 {
|
||||
let last_ix = count.saturating_sub(1);
|
||||
@@ -417,27 +423,31 @@ impl PopupMenu {
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
|
||||
// TODO: fix this
|
||||
#[allow(clippy::only_used_in_recursion)]
|
||||
fn dismiss(&mut self, _: &Dismiss, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.active_submenu().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
|
||||
// Dismiss parent menu, when this menu is dismissed
|
||||
if let Some(parent_menu) = self.parent_menu.clone().and_then(|menu| menu.upgrade()) {
|
||||
parent_menu.update(cx, |view, cx| {
|
||||
view.hovered_menu_ix = None;
|
||||
view.dismiss(&Dismiss, cx);
|
||||
view.dismiss(&Dismiss, window, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn render_keybinding(
|
||||
action: Option<Box<dyn Action>>,
|
||||
cx: &ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if let Some(action) = action {
|
||||
if let Some(keybinding) = cx.bindings_for_action(action.deref()).first() {
|
||||
if let Some(keybinding) = window.bindings_for_action(action.deref()).first() {
|
||||
let el = div()
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
.children(
|
||||
@@ -457,7 +467,8 @@ impl PopupMenu {
|
||||
fn render_icon(
|
||||
has_icon: bool,
|
||||
icon: Option<Icon>,
|
||||
_: &ViewContext<Self>,
|
||||
_window: &Window,
|
||||
_cx: &Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
let icon_placeholder = if has_icon { Some(Icon::empty()) } else { None };
|
||||
|
||||
@@ -487,21 +498,21 @@ impl FluentBuilder for PopupMenu {}
|
||||
|
||||
impl EventEmitter<DismissEvent> for PopupMenu {}
|
||||
|
||||
impl FocusableView for PopupMenu {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
impl Focusable for PopupMenu {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for PopupMenu {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
let has_icon = self.menu_items.iter().any(|item| item.has_icon());
|
||||
let items_count = self.menu_items.len();
|
||||
let max_width = self.max_width;
|
||||
let bounds = self.bounds;
|
||||
|
||||
let window_haft_height = cx.window_bounds().get_bounds().size.height * 0.5;
|
||||
let window_haft_height = window.window_bounds().get_bounds().size.height * 0.5;
|
||||
let max_height = window_haft_height.min(px(450.));
|
||||
|
||||
v_flex()
|
||||
@@ -512,7 +523,9 @@ impl Render for PopupMenu {
|
||||
.on_action(cx.listener(Self::select_prev))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_mouse_down_out(cx.listener(|this, _, cx| this.dismiss(&Dismiss, cx)))
|
||||
.on_mouse_down_out(
|
||||
cx.listener(|this, _, window, cx| this.dismiss(&Dismiss, window, cx)),
|
||||
)
|
||||
.popover_style(cx)
|
||||
.relative()
|
||||
.p_1()
|
||||
@@ -532,8 +545,8 @@ impl Render for PopupMenu {
|
||||
.min_w(rems(8.))
|
||||
.child({
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
@@ -554,10 +567,12 @@ impl Render for PopupMenu {
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
.text_xs()
|
||||
.on_mouse_enter(cx.listener(move |this, _, cx| {
|
||||
this.hovered_menu_ix = Some(ix);
|
||||
cx.notify();
|
||||
}));
|
||||
.on_mouse_enter(cx.listener(
|
||||
move |this, _, _window, cx| {
|
||||
this.hovered_menu_ix = Some(ix);
|
||||
cx.notify();
|
||||
},
|
||||
));
|
||||
|
||||
match item {
|
||||
PopupMenuItem::Separator => {
|
||||
@@ -574,18 +589,20 @@ impl Render for PopupMenu {
|
||||
)
|
||||
}
|
||||
PopupMenuItem::ElementItem { render, .. } => this
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.on_click(ix, cx)
|
||||
}))
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
this.on_click(ix, window, cx)
|
||||
},
|
||||
))
|
||||
.child(
|
||||
h_flex()
|
||||
.min_h(ITEM_HEIGHT)
|
||||
.items_center()
|
||||
.gap_x_1p5()
|
||||
.gap_x_1()
|
||||
.children(Self::render_icon(
|
||||
has_icon, None, cx,
|
||||
has_icon, None, window, cx,
|
||||
))
|
||||
.child((render)(cx)),
|
||||
.child((render)(window, cx)),
|
||||
),
|
||||
PopupMenuItem::Item {
|
||||
icon,
|
||||
@@ -596,11 +613,14 @@ impl Render for PopupMenu {
|
||||
let action = action
|
||||
.as_ref()
|
||||
.map(|action| action.boxed_clone());
|
||||
let key = Self::render_keybinding(action, cx);
|
||||
let key =
|
||||
Self::render_keybinding(action, window, cx);
|
||||
|
||||
this.on_click(cx.listener(move |this, _, cx| {
|
||||
this.on_click(ix, cx)
|
||||
}))
|
||||
this.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
this.on_click(ix, window, cx)
|
||||
},
|
||||
))
|
||||
.child(
|
||||
h_flex()
|
||||
.h(ITEM_HEIGHT)
|
||||
@@ -609,6 +629,7 @@ impl Render for PopupMenu {
|
||||
.children(Self::render_icon(
|
||||
has_icon,
|
||||
icon.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
@@ -637,6 +658,7 @@ impl Render for PopupMenu {
|
||||
.children(Self::render_icon(
|
||||
has_icon,
|
||||
icon.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
@@ -655,7 +677,7 @@ impl Render for PopupMenu {
|
||||
self.hovered_menu_ix,
|
||||
|this, hovered_ix| {
|
||||
let (anchor, left) =
|
||||
if cx.bounds().size.width
|
||||
if window.bounds().size.width
|
||||
- bounds.origin.x
|
||||
< max_width
|
||||
{
|
||||
@@ -670,7 +692,7 @@ impl Render for PopupMenu {
|
||||
|
||||
let top = if bounds.origin.y
|
||||
+ bounds.size.height
|
||||
> cx.bounds().size.height
|
||||
> window.bounds().size.height
|
||||
{
|
||||
px(32.)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
pub use gpui::prelude::*;
|
||||
pub use gpui::{
|
||||
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
|
||||
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext,
|
||||
WindowContext,
|
||||
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, Window,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder, px, relative, IntoElement, ParentElement, RenderOnce, Styled,
|
||||
WindowContext,
|
||||
div, prelude::FluentBuilder, px, relative, App, IntoElement, ParentElement, RenderOnce, Styled,
|
||||
Window,
|
||||
};
|
||||
|
||||
/// A Progress bar element.
|
||||
@@ -32,7 +32,7 @@ impl Default for Progress {
|
||||
}
|
||||
|
||||
impl RenderOnce for Progress {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let rounded = px(self.height / 2.);
|
||||
let relative_w = relative(match self.value {
|
||||
v if v < 0. => 0.,
|
||||
|
||||
@@ -4,11 +4,11 @@ use crate::{
|
||||
IconName,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder, relative, svg, ElementId, InteractiveElement, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext,
|
||||
div, prelude::FluentBuilder, relative, svg, App, ElementId, InteractiveElement, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
|
||||
type OnClick = Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>;
|
||||
type OnClick = Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>;
|
||||
|
||||
/// A Radio element.
|
||||
///
|
||||
@@ -48,14 +48,14 @@ impl Radio {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&bool, &mut WindowContext) + 'static) -> Self {
|
||||
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Radio {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let color = if self.disabled {
|
||||
cx.theme().accent.step(cx, ColorScaleStep::FIVE)
|
||||
} else {
|
||||
@@ -102,8 +102,8 @@ impl RenderOnce for Radio {
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
|this, on_click| {
|
||||
this.on_click(move |_event, cx| {
|
||||
on_click(&!self.checked, cx);
|
||||
this.on_click(move |_event, window, cx| {
|
||||
on_click(&!self.checked, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
use gpui::{Axis, ViewContext};
|
||||
use gpui::{Axis, Context, Window};
|
||||
mod panel;
|
||||
mod resize_handle;
|
||||
|
||||
pub use panel::*;
|
||||
pub(crate) use resize_handle::*;
|
||||
|
||||
pub fn h_resizable(cx: &mut ViewContext<ResizablePanelGroup>) -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new(cx).axis(Axis::Horizontal)
|
||||
pub fn h_resizable(
|
||||
window: &mut Window,
|
||||
cx: &mut Context<ResizablePanelGroup>,
|
||||
) -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new(window, cx).axis(Axis::Horizontal)
|
||||
}
|
||||
|
||||
pub fn v_resizable(cx: &mut ViewContext<ResizablePanelGroup>) -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new(cx).axis(Axis::Vertical)
|
||||
pub fn v_resizable(
|
||||
window: &mut Window,
|
||||
cx: &mut Context<ResizablePanelGroup>,
|
||||
) -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new(window, cx).axis(Axis::Vertical)
|
||||
}
|
||||
|
||||
pub fn resizable_panel() -> ResizablePanel {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::resize_handle;
|
||||
use crate::{h_flex, v_flex, AxisExt};
|
||||
use gpui::{
|
||||
canvas, div, prelude::FluentBuilder, px, relative, Along, AnyElement, AnyView, Axis, Bounds,
|
||||
Element, Entity, EntityId, EventEmitter, IntoElement, IsZero, MouseMoveEvent, MouseUpEvent,
|
||||
ParentElement, Pixels, Render, StatefulInteractiveElement as _, Style, Styled, View,
|
||||
ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||
canvas, div, prelude::FluentBuilder, px, relative, Along, AnyElement, AnyView, App, AppContext,
|
||||
Axis, Bounds, Context, Element, Entity, EntityId, EventEmitter, IntoElement, IsZero,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, StatefulInteractiveElement as _,
|
||||
Style, Styled, WeakEntity, Window,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct DragPanel(pub (EntityId, usize, Axis));
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResizablePanelGroup {
|
||||
panels: Vec<View<ResizablePanel>>,
|
||||
panels: Vec<Entity<ResizablePanel>>,
|
||||
sizes: Vec<Pixels>,
|
||||
axis: Axis,
|
||||
size: Option<Pixels>,
|
||||
@@ -28,7 +28,7 @@ pub struct ResizablePanelGroup {
|
||||
}
|
||||
|
||||
impl ResizablePanelGroup {
|
||||
pub(super) fn new(_cx: &mut ViewContext<Self>) -> Self {
|
||||
pub(super) fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
axis: Axis::Horizontal,
|
||||
sizes: Vec::new(),
|
||||
@@ -39,7 +39,7 @@ impl ResizablePanelGroup {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&mut self, sizes: Vec<Pixels>, panels: Vec<View<ResizablePanel>>) {
|
||||
pub fn load(&mut self, sizes: Vec<Pixels>, panels: Vec<Entity<ResizablePanel>>) {
|
||||
self.sizes = sizes;
|
||||
self.panels = panels;
|
||||
}
|
||||
@@ -50,25 +50,35 @@ impl ResizablePanelGroup {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn set_axis(&mut self, axis: Axis, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn set_axis(&mut self, axis: Axis, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.axis = axis;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Add a resizable panel to the group.
|
||||
pub fn child(mut self, panel: ResizablePanel, cx: &mut ViewContext<Self>) -> Self {
|
||||
self.add_child(panel, cx);
|
||||
pub fn child(
|
||||
mut self,
|
||||
panel: ResizablePanel,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
self.add_child(panel, window, cx);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a ResizablePanelGroup as a child to the group.
|
||||
pub fn group(self, group: ResizablePanelGroup, cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn group(
|
||||
self,
|
||||
group: ResizablePanelGroup,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let group: ResizablePanelGroup = group;
|
||||
let size = group.size;
|
||||
let panel = ResizablePanel::new()
|
||||
.content_view(cx.new_view(|_| group).into())
|
||||
.content_view(cx.new(|_| group).into())
|
||||
.when_some(size, |this, size| this.size(size));
|
||||
self.child(panel, cx)
|
||||
self.child(panel, window, cx)
|
||||
}
|
||||
|
||||
/// Set size of the resizable panel group
|
||||
@@ -90,22 +100,34 @@ impl ResizablePanelGroup {
|
||||
self.sizes.iter().fold(px(0.0), |acc, &size| acc + size)
|
||||
}
|
||||
|
||||
pub fn add_child(&mut self, panel: ResizablePanel, cx: &mut ViewContext<Self>) {
|
||||
pub fn add_child(
|
||||
&mut self,
|
||||
panel: ResizablePanel,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut panel = panel;
|
||||
panel.axis = self.axis;
|
||||
panel.group = Some(cx.view().downgrade());
|
||||
panel.group = Some(cx.model().downgrade());
|
||||
self.sizes.push(panel.initial_size.unwrap_or_default());
|
||||
self.panels.push(cx.new_view(|_| panel));
|
||||
self.panels.push(cx.new(|_| panel));
|
||||
}
|
||||
|
||||
pub fn insert_child(&mut self, panel: ResizablePanel, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
pub fn insert_child(
|
||||
&mut self,
|
||||
panel: ResizablePanel,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut panel = panel;
|
||||
panel.axis = self.axis;
|
||||
panel.group = Some(cx.view().downgrade());
|
||||
panel.group = Some(cx.model().downgrade());
|
||||
|
||||
self.sizes
|
||||
.insert(ix, panel.initial_size.unwrap_or_default());
|
||||
self.panels.insert(ix, cx.new_view(|_| panel));
|
||||
self.panels.insert(ix, cx.new(|_| panel));
|
||||
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
@@ -114,7 +136,8 @@ impl ResizablePanelGroup {
|
||||
&mut self,
|
||||
panel: ResizablePanel,
|
||||
ix: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut panel = panel;
|
||||
|
||||
@@ -125,45 +148,50 @@ impl ResizablePanelGroup {
|
||||
panel.initial_size = old_panel_initial_size;
|
||||
panel.size_ratio = old_panel_size_ratio;
|
||||
panel.axis = self.axis;
|
||||
panel.group = Some(cx.view().downgrade());
|
||||
panel.group = Some(cx.model().downgrade());
|
||||
self.sizes[ix] = panel.initial_size.unwrap_or_default();
|
||||
self.panels[ix] = cx.new_view(|_| panel);
|
||||
self.panels[ix] = cx.new(|_| panel);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn remove_child(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
pub fn remove_child(&mut self, ix: usize, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.sizes.remove(ix);
|
||||
self.panels.remove(ix);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub(crate) fn remove_all_children(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn remove_all_children(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.sizes.clear();
|
||||
self.panels.clear();
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn render_resize_handle(&self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render_resize_handle(
|
||||
&self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
resize_handle(("resizable-handle", ix), self.axis).on_drag(
|
||||
DragPanel((cx.entity_id(), ix, self.axis)),
|
||||
move |drag_panel, _, cx| {
|
||||
move |drag_panel, _, _window, cx| {
|
||||
cx.stop_propagation();
|
||||
// Set current resizing panel ix
|
||||
view.update(cx, |view, _| {
|
||||
view.resizing_panel_ix = Some(ix);
|
||||
});
|
||||
cx.new_view(|_| drag_panel.clone())
|
||||
cx.new(|_| drag_panel.clone())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn done_resizing(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn done_resizing(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(ResizablePanelEvent::Resized);
|
||||
self.resizing_panel_ix = None;
|
||||
}
|
||||
|
||||
fn sync_real_panel_sizes(&mut self, cx: &WindowContext) {
|
||||
fn sync_real_panel_sizes(&mut self, _window: &Window, cx: &App) {
|
||||
for (i, panel) in self.panels.iter().enumerate() {
|
||||
self.sizes[i] = panel.read(cx).bounds.size.along(self.axis)
|
||||
}
|
||||
@@ -171,7 +199,13 @@ impl ResizablePanelGroup {
|
||||
|
||||
/// The `ix`` is the index of the panel to resize,
|
||||
/// and the `size` is the new size for the panel.
|
||||
fn resize_panels(&mut self, ix: usize, size: Pixels, cx: &mut ViewContext<Self>) {
|
||||
fn resize_panels(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
size: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut ix = ix;
|
||||
// Only resize the left panels.
|
||||
if ix >= self.panels.len() - 1 {
|
||||
@@ -180,7 +214,7 @@ impl ResizablePanelGroup {
|
||||
let size = size.floor();
|
||||
let container_size = self.bounds.size.along(self.axis);
|
||||
|
||||
self.sync_real_panel_sizes(cx);
|
||||
self.sync_real_panel_sizes(window, cx);
|
||||
|
||||
let mut changed = size - self.sizes[ix];
|
||||
let is_expand = changed > px(0.);
|
||||
@@ -238,8 +272,8 @@ impl ResizablePanelGroup {
|
||||
impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
|
||||
|
||||
impl Render for ResizablePanelGroup {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
let container = if self.axis.is_horizontal() {
|
||||
h_flex()
|
||||
} else {
|
||||
@@ -250,7 +284,7 @@ impl Render for ResizablePanelGroup {
|
||||
.size_full()
|
||||
.children(self.panels.iter().enumerate().map(|(ix, panel)| {
|
||||
if ix > 0 {
|
||||
let handle = self.render_resize_handle(ix - 1, cx);
|
||||
let handle = self.render_resize_handle(ix - 1, window, cx);
|
||||
panel.update(cx, |view, _| {
|
||||
view.resize_handle = Some(handle.into_any_element())
|
||||
});
|
||||
@@ -260,23 +294,23 @@ impl Render for ResizablePanelGroup {
|
||||
}))
|
||||
.child({
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
})
|
||||
.child(ResizePanelGroupElement {
|
||||
view: cx.view().clone(),
|
||||
view: cx.model().clone(),
|
||||
axis: self.axis,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type ContentBuilder = Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>;
|
||||
type ContentBuilder = Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>;
|
||||
|
||||
pub struct ResizablePanel {
|
||||
group: Option<WeakView<ResizablePanelGroup>>,
|
||||
group: Option<WeakEntity<ResizablePanelGroup>>,
|
||||
/// Initial size is the size that the panel has when it is created.
|
||||
initial_size: Option<Pixels>,
|
||||
/// size is the size that the panel has when it is resized or adjusted by flex layout.
|
||||
@@ -308,7 +342,7 @@ impl ResizablePanel {
|
||||
|
||||
pub fn content<F>(mut self, content: F) -> Self
|
||||
where
|
||||
F: Fn(&mut WindowContext) -> AnyElement + 'static,
|
||||
F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
|
||||
{
|
||||
self.content_builder = Some(Rc::new(content));
|
||||
self
|
||||
@@ -326,12 +360,17 @@ impl ResizablePanel {
|
||||
}
|
||||
|
||||
/// Save the real panel size, and update group sizes
|
||||
fn update_size(&mut self, bounds: Bounds<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
fn update_size(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let new_size = bounds.size.along(self.axis);
|
||||
self.bounds = bounds;
|
||||
self.size = Some(new_size);
|
||||
|
||||
let panel_view = cx.view().clone();
|
||||
let panel_view = cx.model().clone();
|
||||
if let Some(group) = self.group.as_ref() {
|
||||
_ = group.update(cx, |view, _| {
|
||||
if let Some(ix) = view
|
||||
@@ -350,8 +389,8 @@ impl ResizablePanel {
|
||||
impl FluentBuilder for ResizablePanel {}
|
||||
|
||||
impl Render for ResizablePanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.model().clone();
|
||||
let total_size = self
|
||||
.group
|
||||
.as_ref()
|
||||
@@ -388,13 +427,17 @@ impl Render for ResizablePanel {
|
||||
})
|
||||
.child({
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, cx| r.update_size(bounds, cx)),
|
||||
|_, _, _| {},
|
||||
move |bounds, window, cx| {
|
||||
view.update(cx, |r, cx| r.update_size(bounds, window, cx))
|
||||
},
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
})
|
||||
.when_some(self.content_builder.clone(), |this, c| this.child(c(cx)))
|
||||
.when_some(self.content_builder.clone(), |this, c| {
|
||||
this.child(c(window, cx))
|
||||
})
|
||||
.when_some(self.content_view.clone(), |this, c| this.child(c))
|
||||
.when_some(self.resize_handle.take(), |this, c| this.child(c))
|
||||
}
|
||||
@@ -402,7 +445,7 @@ impl Render for ResizablePanel {
|
||||
|
||||
struct ResizePanelGroupElement {
|
||||
axis: Axis,
|
||||
view: View<ResizablePanelGroup>,
|
||||
view: Entity<ResizablePanelGroup>,
|
||||
}
|
||||
|
||||
impl IntoElement for ResizePanelGroupElement {
|
||||
@@ -424,9 +467,10 @@ impl Element for ResizePanelGroupElement {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
(cx.request_layout(Style::default(), None), ())
|
||||
(window.request_layout(Style::default(), None, cx), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -434,7 +478,8 @@ impl Element for ResizePanelGroupElement {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut WindowContext,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
}
|
||||
|
||||
@@ -444,13 +489,14 @@ impl Element for ResizePanelGroupElement {
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
let axis = self.axis;
|
||||
let current_ix = view.read(cx).resizing_panel_ix;
|
||||
move |e: &MouseMoveEvent, phase, cx| {
|
||||
move |e: &MouseMoveEvent, phase, window, cx| {
|
||||
if phase.bubble() {
|
||||
if let Some(ix) = current_ix {
|
||||
view.update(cx, |view, cx| {
|
||||
@@ -461,11 +507,19 @@ impl Element for ResizePanelGroupElement {
|
||||
.read(cx);
|
||||
|
||||
match axis {
|
||||
Axis::Horizontal => {
|
||||
view.resize_panels(ix, e.position.x - panel.bounds.left(), cx)
|
||||
}
|
||||
Axis::Horizontal => view.resize_panels(
|
||||
ix,
|
||||
e.position.x - panel.bounds.left(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
Axis::Vertical => {
|
||||
view.resize_panels(ix, e.position.y - panel.bounds.top(), cx);
|
||||
view.resize_panels(
|
||||
ix,
|
||||
e.position.y - panel.bounds.top(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -475,11 +529,11 @@ impl Element for ResizePanelGroupElement {
|
||||
});
|
||||
|
||||
// When any mouse up, stop dragging
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
move |_: &MouseUpEvent, phase, cx| {
|
||||
move |_: &MouseUpEvent, phase, window, cx| {
|
||||
if phase.bubble() {
|
||||
view.update(cx, |view, cx| view.done_resizing(cx));
|
||||
view.update(cx, |view, cx| view.done_resizing(window, cx));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,9 +3,9 @@ use crate::{
|
||||
AxisExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, Axis, Div, ElementId, InteractiveElement, IntoElement,
|
||||
ParentElement as _, Pixels, RenderOnce, Stateful, StatefulInteractiveElement, Styled as _,
|
||||
WindowContext,
|
||||
div, prelude::FluentBuilder as _, px, App, Axis, Div, ElementId, InteractiveElement,
|
||||
IntoElement, ParentElement as _, Pixels, RenderOnce, Stateful, StatefulInteractiveElement,
|
||||
Styled as _, Window,
|
||||
};
|
||||
|
||||
pub(crate) const HANDLE_PADDING: Pixels = px(8.);
|
||||
@@ -40,7 +40,7 @@ impl InteractiveElement for ResizeHandle {
|
||||
impl StatefulInteractiveElement for ResizeHandle {}
|
||||
|
||||
impl RenderOnce for ResizeHandle {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
self.base
|
||||
.occlude()
|
||||
.absolute()
|
||||
|
||||
@@ -1,55 +1,52 @@
|
||||
use crate::{
|
||||
modal::Modal,
|
||||
notification::{Notification, NotificationList},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme, Theme},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
window_border,
|
||||
};
|
||||
use gpui::{
|
||||
div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled,
|
||||
View, ViewContext, VisualContext as _, WindowContext,
|
||||
};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
div, AnyView, App, AppContext, Context, DefiniteLength, Entity, FocusHandle,
|
||||
InteractiveElement, IntoElement, ParentElement as _, Render, Styled, Window,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
|
||||
pub trait ContextModal: Sized {
|
||||
/// Opens a Modal.
|
||||
fn open_modal<F>(&mut self, build: F)
|
||||
fn open_modal<F>(&mut self, cx: &mut App, build: F)
|
||||
where
|
||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static;
|
||||
|
||||
F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static;
|
||||
/// Return true, if there is an active Modal.
|
||||
fn has_active_modal(&self) -> bool;
|
||||
fn has_active_modal(&mut self, cx: &mut App) -> bool;
|
||||
|
||||
/// Closes the last active Modal.
|
||||
fn close_modal(&mut self);
|
||||
fn close_modal(&mut self, cx: &mut App);
|
||||
|
||||
/// Closes all active Modals.
|
||||
fn close_all_modals(&mut self);
|
||||
fn close_all_modals(&mut self, cx: &mut App);
|
||||
|
||||
/// Returns number of notifications.
|
||||
fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>>;
|
||||
|
||||
/// Pushes a notification to the notification list.
|
||||
fn push_notification(&mut self, note: impl Into<Notification>);
|
||||
fn clear_notifications(&mut self);
|
||||
/// Returns number of notifications.
|
||||
fn notifications(&self) -> Rc<Vec<View<Notification>>>;
|
||||
fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);
|
||||
fn clear_notifications(&mut self, cx: &mut App);
|
||||
}
|
||||
|
||||
impl ContextModal for WindowContext<'_> {
|
||||
fn open_modal<F>(&mut self, build: F)
|
||||
impl ContextModal for Window {
|
||||
fn open_modal<F>(&mut self, cx: &mut App, build: F)
|
||||
where
|
||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
||||
F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static,
|
||||
{
|
||||
Root::update(self, move |root, cx| {
|
||||
Root::update(self, cx, move |root, window, cx| {
|
||||
// Only save focus handle if there are no active modals.
|
||||
// This is used to restore focus when all modals are closed.
|
||||
if root.active_modals.is_empty() {
|
||||
root.previous_focus_handle = cx.focused();
|
||||
root.previous_focus_handle = window.focused(cx);
|
||||
}
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
focus_handle.focus(cx);
|
||||
focus_handle.focus(window);
|
||||
|
||||
root.active_modals.push(ActiveModal {
|
||||
focus_handle,
|
||||
@@ -59,86 +56,112 @@ impl ContextModal for WindowContext<'_> {
|
||||
})
|
||||
}
|
||||
|
||||
fn has_active_modal(&self) -> bool {
|
||||
!Root::read(self).active_modals.is_empty()
|
||||
fn has_active_modal(&mut self, cx: &mut App) -> bool {
|
||||
!Root::read(self, cx).active_modals.is_empty()
|
||||
}
|
||||
|
||||
fn close_modal(&mut self) {
|
||||
Root::update(self, move |root, cx| {
|
||||
fn close_modal(&mut self, cx: &mut App) {
|
||||
Root::update(self, cx, move |root, window, cx| {
|
||||
root.active_modals.pop();
|
||||
|
||||
if let Some(top_modal) = root.active_modals.last() {
|
||||
// Focus the next modal.
|
||||
top_modal.focus_handle.focus(cx);
|
||||
top_modal.focus_handle.focus(window);
|
||||
} else {
|
||||
// Restore focus if there are no more modals.
|
||||
root.focus_back(cx);
|
||||
root.focus_back(window, cx);
|
||||
}
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
fn close_all_modals(&mut self) {
|
||||
Root::update(self, |root, cx| {
|
||||
fn close_all_modals(&mut self, cx: &mut App) {
|
||||
Root::update(self, cx, |root, window, cx| {
|
||||
root.active_modals.clear();
|
||||
root.focus_back(cx);
|
||||
root.focus_back(window, cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
fn push_notification(&mut self, note: impl Into<Notification>) {
|
||||
fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App) {
|
||||
let note = note.into();
|
||||
Root::update(self, move |root, cx| {
|
||||
root.notification.update(cx, |view, cx| view.push(note, cx));
|
||||
Root::update(self, cx, move |root, window, cx| {
|
||||
root.notification
|
||||
.update(cx, |view, cx| view.push(note, window, cx));
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
fn clear_notifications(&mut self) {
|
||||
Root::update(self, move |root, cx| {
|
||||
root.notification.update(cx, |view, cx| view.clear(cx));
|
||||
fn clear_notifications(&mut self, cx: &mut App) {
|
||||
Root::update(self, cx, move |root, window, cx| {
|
||||
root.notification
|
||||
.update(cx, |view, cx| view.clear(window, cx));
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
fn notifications(&self) -> Rc<Vec<View<Notification>>> {
|
||||
Rc::new(Root::read(self).notification.read(self).notifications())
|
||||
fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
|
||||
let entity = Root::read(self, cx).notification.clone();
|
||||
Rc::new(entity.read(cx).notifications())
|
||||
}
|
||||
}
|
||||
impl<V> ContextModal for ViewContext<'_, V> {
|
||||
fn has_active_modal(&self) -> bool {
|
||||
self.deref().has_active_modal()
|
||||
}
|
||||
|
||||
fn open_modal<F>(&mut self, build: F)
|
||||
where
|
||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
||||
{
|
||||
self.deref_mut().open_modal(build)
|
||||
}
|
||||
// impl<V> ContextModal for Context<'_, V> {
|
||||
// fn open_drawer<F>(&mut self, cx: &mut App, build: F)
|
||||
// where
|
||||
// F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static,
|
||||
// {
|
||||
// self.deref_mut().open_drawer(cx, build)
|
||||
// }
|
||||
|
||||
/// Close the last active modal.
|
||||
fn close_modal(&mut self) {
|
||||
self.deref_mut().close_modal()
|
||||
}
|
||||
// fn open_drawer_at<F>(&mut self, cx: &mut App, placement: Placement, build: F)
|
||||
// where
|
||||
// F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static,
|
||||
// {
|
||||
// self.deref_mut().open_drawer_at(cx, placement, build)
|
||||
// }
|
||||
|
||||
/// Close all modals.
|
||||
fn close_all_modals(&mut self) {
|
||||
self.deref_mut().close_all_modals()
|
||||
}
|
||||
// fn has_active_modal(&self, cx: &mut App) -> bool {
|
||||
// self.deref().has_active_modal(cx)
|
||||
// }
|
||||
|
||||
fn push_notification(&mut self, note: impl Into<Notification>) {
|
||||
self.deref_mut().push_notification(note)
|
||||
}
|
||||
// fn close_drawer(&mut self, cx: &mut App) {
|
||||
// self.deref_mut().close_drawer(cx)
|
||||
// }
|
||||
|
||||
fn clear_notifications(&mut self) {
|
||||
self.deref_mut().clear_notifications()
|
||||
}
|
||||
// fn open_modal<F>(&mut self, cx: &mut App, build: F)
|
||||
// where
|
||||
// F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static,
|
||||
// {
|
||||
// self.deref_mut().open_modal(cx, build)
|
||||
// }
|
||||
|
||||
fn notifications(&self) -> Rc<Vec<View<Notification>>> {
|
||||
self.deref().notifications()
|
||||
}
|
||||
}
|
||||
// fn has_active_drawer(&self, cx: &mut App) -> bool {
|
||||
// self.deref().has_active_drawer(cx)
|
||||
// }
|
||||
|
||||
// /// Close the last active modal.
|
||||
// fn close_modal(&mut self, cx: &mut App) {
|
||||
// self.deref_mut().close_modal(cx)
|
||||
// }
|
||||
|
||||
// /// Close all modals.
|
||||
// fn close_all_modals(&mut self, cx: &mut App) {
|
||||
// self.deref_mut().close_all_modals(cx)
|
||||
// }
|
||||
|
||||
// fn push_notification(&mut self, cx: &mut App, note: impl Into<Notification>) {
|
||||
// self.deref_mut().push_notification(cx, note)
|
||||
// }
|
||||
|
||||
// fn clear_notifications(&mut self, cx: &mut App) {
|
||||
// self.deref_mut().clear_notifications(cx)
|
||||
// }
|
||||
|
||||
// fn notifications(&self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
|
||||
// self.deref().notifications(cx)
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Root is a view for the App window for as the top level view (Must be the first view in the window).
|
||||
///
|
||||
@@ -148,80 +171,62 @@ pub struct Root {
|
||||
/// When the Modal, Drawer closes, we will focus back to the previous view.
|
||||
previous_focus_handle: Option<FocusHandle>,
|
||||
active_modals: Vec<ActiveModal>,
|
||||
pub notification: View<NotificationList>,
|
||||
pub notification: Entity<NotificationList>,
|
||||
view: AnyView,
|
||||
}
|
||||
|
||||
type ModelBuilder = Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ActiveModal {
|
||||
focus_handle: FocusHandle,
|
||||
builder: ModelBuilder,
|
||||
builder: Rc<dyn Fn(Modal, &mut Window, &mut App) -> Modal + 'static>,
|
||||
}
|
||||
|
||||
impl Root {
|
||||
pub fn new(view: AnyView, cx: &mut ViewContext<Self>) -> Self {
|
||||
cx.observe_window_appearance(|_, cx| {
|
||||
Theme::sync_system_appearance(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
pub fn new(view: AnyView, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
previous_focus_handle: None,
|
||||
active_modals: Vec::new(),
|
||||
notification: cx.new_view(NotificationList::new),
|
||||
notification: cx.new(|cx| NotificationList::new(window, cx)),
|
||||
view,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update<F>(cx: &mut WindowContext, f: F)
|
||||
pub fn update<F>(window: &mut Window, cx: &mut App, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Self, &mut ViewContext<Self>) + 'static,
|
||||
F: FnOnce(&mut Self, &mut Window, &mut Context<Self>) + 'static,
|
||||
{
|
||||
let root = cx
|
||||
.window_handle()
|
||||
.downcast::<Root>()
|
||||
.and_then(|w| w.root_view(cx).ok())
|
||||
.expect("The window root view should be of type `ui::Root`.");
|
||||
|
||||
root.update(cx, |root, cx| f(root, cx))
|
||||
if let Some(Some(root)) = window.root_model::<Root>() {
|
||||
root.update(cx, |root, cx| f(root, window, cx));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<'a>(cx: &'a WindowContext) -> &'a Self {
|
||||
let root = cx
|
||||
.window_handle()
|
||||
.downcast::<Root>()
|
||||
.and_then(|w| w.root_view(cx).ok())
|
||||
.expect("The window root view should be of type `ui::Root`.");
|
||||
|
||||
root.read(cx)
|
||||
pub fn read<'a>(window: &'a mut Window, cx: &'a mut App) -> &'a Self {
|
||||
window
|
||||
.root_model::<Root>()
|
||||
.expect("The window root view should be of type `ui::Root`.")
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
}
|
||||
|
||||
fn focus_back(&mut self, cx: &mut WindowContext) {
|
||||
fn focus_back(&mut self, window: &mut Window, _: &mut App) {
|
||||
if let Some(handle) = self.previous_focus_handle.clone() {
|
||||
cx.focus(&handle);
|
||||
window.focus(&handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Render Notification layer.
|
||||
pub fn render_notification_layer(cx: &mut WindowContext) -> Option<impl IntoElement> {
|
||||
let root = cx
|
||||
.window_handle()
|
||||
.downcast::<Root>()
|
||||
.and_then(|w| w.root_view(cx).ok())
|
||||
.expect("The window root view should be of type `ui::Root`.");
|
||||
pub fn render_notification_layer(
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<impl IntoElement> {
|
||||
let root = window.root_model::<Root>()??;
|
||||
|
||||
Some(div().child(root.read(cx).notification.clone()))
|
||||
}
|
||||
|
||||
/// Render the Modal layer.
|
||||
pub fn render_modal_layer(cx: &mut WindowContext) -> Option<impl IntoElement> {
|
||||
let root = cx
|
||||
.window_handle()
|
||||
.downcast::<Root>()
|
||||
.and_then(|w| w.root_view(cx).ok())
|
||||
.expect("The window root view should be of type `ui::Root`.");
|
||||
pub fn render_modal_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
|
||||
let root = window.root_model::<Root>()??;
|
||||
|
||||
let active_modals = root.read(cx).active_modals.clone();
|
||||
let mut has_overlay = false;
|
||||
@@ -232,9 +237,9 @@ impl Root {
|
||||
|
||||
Some(
|
||||
div().children(active_modals.iter().enumerate().map(|(i, active_modal)| {
|
||||
let mut modal = Modal::new(cx);
|
||||
let mut modal = Modal::new(window, cx);
|
||||
|
||||
modal = (active_modal.builder)(modal, cx);
|
||||
modal = (active_modal.builder)(modal, window, cx);
|
||||
modal.layer_ix = i;
|
||||
// Give the modal the focus handle, because `modal` is a temporary value, is not possible to
|
||||
// keep the focus handle in the modal.
|
||||
@@ -262,9 +267,9 @@ impl Root {
|
||||
}
|
||||
|
||||
impl Render for Root {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let base_font_size = cx.theme().font_size;
|
||||
cx.set_rem_size(base_font_size);
|
||||
window.set_rem_size(base_font_size);
|
||||
|
||||
window_border().child(
|
||||
div()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
canvas, div, relative, AnyElement, Div, Element, ElementId, EntityId, GlobalElementId,
|
||||
canvas, div, relative, AnyElement, App, Div, Element, ElementId, EntityId, GlobalElementId,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString,
|
||||
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, WindowContext,
|
||||
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, Window,
|
||||
};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
@@ -58,14 +58,18 @@ where
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
id: &GlobalElementId,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut WindowContext) -> R,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut Window, &mut App) -> R,
|
||||
) -> R {
|
||||
cx.with_optional_element_state::<ScrollViewState, _>(Some(id), |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, cx);
|
||||
(result, Some(element_state))
|
||||
})
|
||||
window.with_optional_element_state::<ScrollViewState, _>(
|
||||
Some(id),
|
||||
|element_state, window| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, window, cx);
|
||||
(result, Some(element_state))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,14 +153,16 @@ where
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut gpui::WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let style = Style {
|
||||
flex_grow: 1.0,
|
||||
position: Position::Relative,
|
||||
flex_grow: 1.0,
|
||||
flex_shrink: 1.0,
|
||||
size: Size {
|
||||
width: relative(1.0).into(),
|
||||
height: relative(1.0).into(),
|
||||
width: relative(1.).into(),
|
||||
height: relative(1.).into(),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
@@ -167,7 +173,7 @@ where
|
||||
let scroll_id = self.id.clone();
|
||||
let content = self.element.take().map(|c| c.into_any_element());
|
||||
|
||||
self.with_element_state(id.unwrap(), cx, |_, element_state, cx| {
|
||||
self.with_element_state(id.unwrap(), window, cx, |_, element_state, window, cx| {
|
||||
let handle = element_state.handle.clone();
|
||||
let state = element_state.state.clone();
|
||||
let scroll_size = element_state.scroll_size.clone();
|
||||
@@ -185,7 +191,7 @@ where
|
||||
.size_full()
|
||||
.child(div().children(content).child({
|
||||
let scroll_size = element_state.scroll_size.clone();
|
||||
canvas(move |b, _| scroll_size.set(b.size), |_, _, _| {})
|
||||
canvas(move |b, _, _| scroll_size.set(b.size), |_, _, _, _| {})
|
||||
.absolute()
|
||||
.size_full()
|
||||
})),
|
||||
@@ -203,9 +209,9 @@ where
|
||||
),
|
||||
)
|
||||
.into_any_element();
|
||||
let element_id = element.request_layout(window, cx);
|
||||
|
||||
let element_id = element.request_layout(cx);
|
||||
let layout_id = cx.request_layout(style, vec![element_id]);
|
||||
let layout_id = window.request_layout(style, vec![element_id], cx);
|
||||
|
||||
(layout_id, element)
|
||||
})
|
||||
@@ -216,9 +222,10 @@ where
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
element.prepaint(cx);
|
||||
element.prepaint(window, cx);
|
||||
// do nothing
|
||||
ScrollViewState::default()
|
||||
}
|
||||
@@ -229,8 +236,9 @@ where
|
||||
_: gpui::Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
element.paint(cx)
|
||||
element.paint(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
px, relative, Axis, Bounds, ContentMask, Corners, Edges, Element, ElementId, EntityId,
|
||||
px, relative, App, Axis, Bounds, ContentMask, Corners, Edges, Element, ElementId, EntityId,
|
||||
GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point,
|
||||
Position, ScrollHandle, ScrollWheelEvent, Size, Style, WindowContext,
|
||||
Position, ScrollHandle, ScrollWheelEvent, Size, Style, Window,
|
||||
};
|
||||
|
||||
use crate::AxisExt;
|
||||
@@ -56,9 +56,9 @@ impl Element for ScrollableMask {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
// Set the layout style relative to the table view to get same size.
|
||||
let style = Style {
|
||||
position: Position::Absolute,
|
||||
flex_grow: 1.0,
|
||||
@@ -70,7 +70,7 @@ impl Element for ScrollableMask {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
(cx.request_layout(style, None), ())
|
||||
(window.request_layout(style, None, cx), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -78,7 +78,8 @@ impl Element for ScrollableMask {
|
||||
_: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
_: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
// Move y to bounds height to cover the parent view.
|
||||
let cover_bounds = Bounds {
|
||||
@@ -89,7 +90,7 @@ impl Element for ScrollableMask {
|
||||
size: bounds.size,
|
||||
};
|
||||
|
||||
cx.insert_hitbox(cover_bounds, false)
|
||||
window.insert_hitbox(cover_bounds, false)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
@@ -98,14 +99,15 @@ impl Element for ScrollableMask {
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
_: &mut App,
|
||||
) {
|
||||
let line_height = cx.line_height();
|
||||
let line_height = window.line_height();
|
||||
let bounds = hitbox.bounds;
|
||||
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
if let Some(color) = self.debug {
|
||||
cx.paint_quad(PaintQuad {
|
||||
window.paint_quad(PaintQuad {
|
||||
bounds,
|
||||
border_widths: Edges::all(px(1.0)),
|
||||
border_color: color,
|
||||
@@ -114,16 +116,19 @@ impl Element for ScrollableMask {
|
||||
});
|
||||
}
|
||||
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let view_id = self.view_id;
|
||||
let is_horizontal = self.axis.is_horizontal();
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
let hitbox = hitbox.clone();
|
||||
let mouse_position = cx.mouse_position();
|
||||
let mouse_position = window.mouse_position();
|
||||
let last_offset = scroll_handle.offset();
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if bounds.contains(&mouse_position) && phase.bubble() && hitbox.is_hovered(cx) {
|
||||
move |event: &ScrollWheelEvent, phase, window, cx| {
|
||||
if bounds.contains(&mouse_position)
|
||||
&& phase.bubble()
|
||||
&& hitbox.is_hovered(window)
|
||||
{
|
||||
let mut offset = scroll_handle.offset();
|
||||
let mut delta = event.delta.pixel_delta(line_height);
|
||||
|
||||
@@ -146,7 +151,7 @@ impl Element for ScrollableMask {
|
||||
|
||||
if last_offset != offset {
|
||||
scroll_handle.set_offset(offset);
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ impl Scrollbar {
|
||||
self
|
||||
}
|
||||
|
||||
fn style_for_active(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_hover,
|
||||
cx.theme().scrollbar,
|
||||
@@ -300,7 +300,7 @@ impl Scrollbar {
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_hovered_thumb(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
fn style_for_hovered_thumb(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_hover,
|
||||
cx.theme().scrollbar,
|
||||
@@ -310,7 +310,7 @@ impl Scrollbar {
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_hovered_bar(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
fn style_for_hovered_bar(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
let (inset, radius) = if cx.theme().scrollbar_show.is_hover() {
|
||||
(THUMB_INSET, THUMB_RADIUS - px(1.))
|
||||
} else {
|
||||
@@ -326,7 +326,7 @@ impl Scrollbar {
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_idle(_: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
fn style_for_idle(_: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
(
|
||||
gpui::transparent_black(),
|
||||
gpui::transparent_black(),
|
||||
@@ -379,7 +379,8 @@ impl Element for Scrollbar {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut gpui::WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let style = Style {
|
||||
position: Position::Absolute,
|
||||
@@ -392,7 +393,7 @@ impl Element for Scrollbar {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
(cx.request_layout(style, None), ())
|
||||
(window.request_layout(style, None, cx), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -400,10 +401,11 @@ impl Element for Scrollbar {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
let hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
cx.insert_hitbox(bounds, false)
|
||||
let hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
window.insert_hitbox(bounds, false)
|
||||
});
|
||||
|
||||
let mut states = vec![];
|
||||
@@ -507,7 +509,7 @@ impl Element for Scrollbar {
|
||||
idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity);
|
||||
};
|
||||
|
||||
cx.request_animation_frame();
|
||||
window.request_animation_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,8 +552,8 @@ impl Element for Scrollbar {
|
||||
)
|
||||
};
|
||||
|
||||
let bar_hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
cx.insert_hitbox(bounds, false)
|
||||
let bar_hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
window.insert_hitbox(bounds, false)
|
||||
});
|
||||
|
||||
states.push(AxisPrepaintState {
|
||||
@@ -580,7 +582,8 @@ impl Element for Scrollbar {
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let hitbox_bounds = prepaint.hitbox.bounds;
|
||||
let is_visible = self.state.get().is_scrollbar_visible();
|
||||
@@ -597,9 +600,9 @@ impl Element for Scrollbar {
|
||||
let margin_end = state.margin_end;
|
||||
let is_vertical = axis.is_vertical();
|
||||
|
||||
cx.set_cursor_style(CursorStyle::default(), &state.bar_hitbox);
|
||||
window.set_cursor_style(CursorStyle::default(), &state.bar_hitbox);
|
||||
|
||||
cx.paint_layer(hitbox_bounds, |cx| {
|
||||
window.paint_layer(hitbox_bounds, |cx| {
|
||||
cx.paint_quad(fill(state.bounds, state.bg));
|
||||
|
||||
cx.paint_quad(PaintQuad {
|
||||
@@ -627,12 +630,12 @@ impl Element for Scrollbar {
|
||||
cx.paint_quad(fill(state.thumb_fill_bounds, state.thumb_bg).corner_radii(radius));
|
||||
});
|
||||
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let state = self.state.clone();
|
||||
let view_id = self.view_id;
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, cx| {
|
||||
move |event: &ScrollWheelEvent, phase, _, cx| {
|
||||
if phase.bubble()
|
||||
&& hitbox_bounds.contains(&event.position)
|
||||
&& scroll_handle.offset() != state.get().last_scroll_offset
|
||||
@@ -642,7 +645,7 @@ impl Element for Scrollbar {
|
||||
.get()
|
||||
.with_last_scroll(scroll_handle.offset(), Some(Instant::now())),
|
||||
);
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -650,12 +653,12 @@ impl Element for Scrollbar {
|
||||
let safe_range = (-scroll_area_size + container_size)..px(0.);
|
||||
|
||||
if is_hover_to_show || is_visible {
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let state = self.state.clone();
|
||||
let view_id = self.view_id;
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
move |event: &MouseDownEvent, phase, _, cx| {
|
||||
if phase.bubble() && bounds.contains(&event.position) {
|
||||
cx.stop_propagation();
|
||||
|
||||
@@ -665,7 +668,7 @@ impl Element for Scrollbar {
|
||||
|
||||
state.set(state.get().with_drag_pos(axis, pos));
|
||||
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
} else {
|
||||
// click on the scrollbar, jump to the position
|
||||
// Set the thumb bar center to the click position
|
||||
@@ -698,34 +701,34 @@ impl Element for Scrollbar {
|
||||
});
|
||||
}
|
||||
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
let state = self.state.clone();
|
||||
let view_id = self.view_id;
|
||||
|
||||
move |event: &MouseMoveEvent, _, cx| {
|
||||
move |event: &MouseMoveEvent, _, _, cx| {
|
||||
// Update hovered state for scrollbar
|
||||
if bounds.contains(&event.position) {
|
||||
if state.get().hovered_axis != Some(axis) {
|
||||
state.set(state.get().with_hovered(Some(axis)));
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
} else if state.get().hovered_axis == Some(axis)
|
||||
&& state.get().hovered_axis.is_some()
|
||||
{
|
||||
state.set(state.get().with_hovered(None));
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
|
||||
// Update hovered state for scrollbar thumb
|
||||
if thumb_bounds.contains(&event.position) {
|
||||
if state.get().hovered_on_thumb != Some(axis) {
|
||||
state.set(state.get().with_hovered_on_thumb(Some(axis)));
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
} else if state.get().hovered_on_thumb == Some(axis) {
|
||||
state.set(state.get().with_hovered_on_thumb(None));
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
|
||||
// Move thumb position on dragging
|
||||
@@ -757,20 +760,24 @@ impl Element for Scrollbar {
|
||||
)
|
||||
};
|
||||
|
||||
scroll_handle.set_offset(offset);
|
||||
cx.notify(Some(view_id));
|
||||
if (scroll_handle.offset().y - offset.y).abs() > px(1.)
|
||||
|| (scroll_handle.offset().x - offset.x).abs() > px(1.)
|
||||
{
|
||||
scroll_handle.set_offset(offset);
|
||||
cx.notify(view_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.on_mouse_event({
|
||||
window.on_mouse_event({
|
||||
let view_id = self.view_id;
|
||||
let state = self.state.clone();
|
||||
|
||||
move |_event: &MouseUpEvent, phase, cx| {
|
||||
move |_event: &MouseUpEvent, phase, _, cx| {
|
||||
if phase.bubble() {
|
||||
state.set(state.get().with_unset_drag_pos());
|
||||
cx.notify(Some(view_id));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ impl Styled for Skeleton {
|
||||
}
|
||||
|
||||
impl RenderOnce for Skeleton {
|
||||
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
div().child(
|
||||
self.base
|
||||
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
scroll::{Scrollable, ScrollbarAxis},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
};
|
||||
use gpui::{div, px, Axis, Div, Element, ElementId, EntityId, Pixels, Styled, WindowContext};
|
||||
use gpui::{div, px, App, Axis, Div, Element, ElementId, EntityId, Pixels, Styled, Window};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
@@ -38,7 +38,7 @@ pub trait StyledExt: Styled + Sized {
|
||||
}
|
||||
|
||||
/// Render a border with a width of 1px, color ring color
|
||||
fn outline(self, cx: &WindowContext) -> Self {
|
||||
fn outline(self, _window: &Window, cx: &App) -> Self {
|
||||
self.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ pub trait StyledExt: Styled + Sized {
|
||||
font_weight!(font_black, BLACK);
|
||||
|
||||
/// Set as Popover style
|
||||
fn popover_style(self, cx: &mut WindowContext) -> Self {
|
||||
fn popover_style(self, cx: &mut App) -> Self {
|
||||
self.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
|
||||
|
||||
@@ -4,13 +4,13 @@ use crate::{
|
||||
Disableable, Side, Sizable, Size,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, Element,
|
||||
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, App, Element,
|
||||
ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _,
|
||||
SharedString, Styled as _, WindowContext,
|
||||
SharedString, Styled as _, Window,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
type OnClick = Option<Rc<dyn Fn(&bool, &mut WindowContext)>>;
|
||||
type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>;
|
||||
|
||||
pub struct Switch {
|
||||
id: ElementId,
|
||||
@@ -48,7 +48,7 @@ impl Switch {
|
||||
|
||||
pub fn on_click<F>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: Fn(&bool, &mut WindowContext) + 'static,
|
||||
F: Fn(&bool, &mut Window, &mut App) + 'static,
|
||||
{
|
||||
self.on_click = Some(Rc::new(handler));
|
||||
self
|
||||
@@ -99,9 +99,10 @@ impl Element for Switch {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
cx.with_element_state::<SwitchState, _>(global_id.unwrap(), move |state, cx| {
|
||||
window.with_element_state::<SwitchState, _>(global_id.unwrap(), move |state, window| {
|
||||
let state = state.unwrap_or_default();
|
||||
|
||||
let theme = cx.theme();
|
||||
@@ -131,92 +132,95 @@ impl Element for Switch {
|
||||
};
|
||||
let inset = px(2.);
|
||||
|
||||
let mut element = h_flex()
|
||||
.id(self.id.clone())
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.when(self.label_side.is_left(), |this| this.flex_row_reverse())
|
||||
let mut element = div()
|
||||
.flex()
|
||||
.child(
|
||||
// Switch Bar
|
||||
div()
|
||||
h_flex()
|
||||
.id(self.id.clone())
|
||||
.w(bg_width)
|
||||
.h(bg_height)
|
||||
.rounded(bg_height / 2.)
|
||||
.flex()
|
||||
.items_center()
|
||||
.border(inset)
|
||||
.border_color(theme.transparent)
|
||||
.bg(bg)
|
||||
.when(!self.disabled, |this| this.cursor_pointer())
|
||||
.gap_2()
|
||||
.when(self.label_side.is_left(), |this| this.flex_row_reverse())
|
||||
.child(
|
||||
// Switch Toggle
|
||||
// Switch Bar
|
||||
div()
|
||||
.rounded_full()
|
||||
.bg(toggle_bg)
|
||||
.size(bar_width)
|
||||
.map(|this| {
|
||||
let prev_checked = state.prev_checked.clone();
|
||||
if !self.disabled
|
||||
&& prev_checked
|
||||
.borrow()
|
||||
.map_or(false, |prev| prev != checked)
|
||||
{
|
||||
let dur = Duration::from_secs_f64(0.15);
|
||||
cx.spawn(|cx| async move {
|
||||
cx.background_executor().timer(dur).await;
|
||||
.id(self.id.clone())
|
||||
.w(bg_width)
|
||||
.h(bg_height)
|
||||
.rounded(bg_height / 2.)
|
||||
.flex()
|
||||
.items_center()
|
||||
.border(inset)
|
||||
.border_color(theme.transparent)
|
||||
.bg(bg)
|
||||
.when(!self.disabled, |this| this.cursor_pointer())
|
||||
.child(
|
||||
// Switch Toggle
|
||||
div().rounded_full().bg(toggle_bg).size(bar_width).map(
|
||||
|this| {
|
||||
let prev_checked = state.prev_checked.clone();
|
||||
if !self.disabled
|
||||
&& prev_checked
|
||||
.borrow()
|
||||
.map_or(false, |prev| prev != checked)
|
||||
{
|
||||
let dur = Duration::from_secs_f64(0.15);
|
||||
cx.spawn(|cx| async move {
|
||||
cx.background_executor().timer(dur).await;
|
||||
|
||||
*prev_checked.borrow_mut() = Some(checked);
|
||||
})
|
||||
.detach();
|
||||
this.with_animation(
|
||||
ElementId::NamedInteger(
|
||||
"move".into(),
|
||||
checked as usize,
|
||||
),
|
||||
Animation::new(dur),
|
||||
move |this, delta| {
|
||||
*prev_checked.borrow_mut() = Some(checked);
|
||||
})
|
||||
.detach();
|
||||
this.with_animation(
|
||||
ElementId::NamedInteger(
|
||||
"move".into(),
|
||||
checked as usize,
|
||||
),
|
||||
Animation::new(dur),
|
||||
move |this, delta| {
|
||||
let max_x =
|
||||
bg_width - bar_width - inset * 2;
|
||||
let x = if checked {
|
||||
max_x * delta
|
||||
} else {
|
||||
max_x - max_x * delta
|
||||
};
|
||||
this.left(x)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
let max_x = bg_width - bar_width - inset * 2;
|
||||
let x = if checked {
|
||||
max_x * delta
|
||||
} else {
|
||||
max_x - max_x * delta
|
||||
};
|
||||
this.left(x)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
let max_x = bg_width - bar_width - inset * 2;
|
||||
let x = if checked { max_x } else { px(0.) };
|
||||
this.left(x).into_any_element()
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.when_some(self.label.clone(), |this, label| {
|
||||
this.child(div().child(label).map(|this| match self.size {
|
||||
Size::XSmall | Size::Small => this.text_sm(),
|
||||
_ => this.text_base(),
|
||||
}))
|
||||
})
|
||||
.when_some(
|
||||
on_click
|
||||
.as_ref()
|
||||
.map(|c| c.clone())
|
||||
.filter(|_| !self.disabled),
|
||||
|this, on_click| {
|
||||
let prev_checked = state.prev_checked.clone();
|
||||
this.on_mouse_down(gpui::MouseButton::Left, move |_, cx| {
|
||||
cx.stop_propagation();
|
||||
*prev_checked.borrow_mut() = Some(checked);
|
||||
on_click(&!checked, cx);
|
||||
let x = if checked { max_x } else { px(0.) };
|
||||
this.left(x).into_any_element()
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.when_some(self.label.clone(), |this, label| {
|
||||
this.child(div().child(label).map(|this| match self.size {
|
||||
Size::XSmall | Size::Small => this.text_sm(),
|
||||
_ => this.text_base(),
|
||||
}))
|
||||
})
|
||||
},
|
||||
.when_some(
|
||||
on_click
|
||||
.as_ref()
|
||||
.map(|c| c.clone())
|
||||
.filter(|_| !self.disabled),
|
||||
|this, on_click| {
|
||||
let prev_checked = state.prev_checked.clone();
|
||||
this.on_mouse_down(gpui::MouseButton::Left, move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
*prev_checked.borrow_mut() = Some(checked);
|
||||
on_click(&!checked, window, cx);
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
((element.request_layout(cx), element), state)
|
||||
((element.request_layout(window, cx), element), state)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -225,9 +229,10 @@ impl Element for Switch {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<gpui::Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
element.prepaint(cx);
|
||||
element.prepaint(window, cx);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
@@ -236,8 +241,9 @@ impl Element for Switch {
|
||||
_: gpui::Bounds<gpui::Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
element.paint(cx)
|
||||
element.paint(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ impl Styled for Tab {
|
||||
}
|
||||
|
||||
impl RenderOnce for Tab {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
|
||||
(true, false) => (
|
||||
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::h_flex;
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, AnyElement, Div, ElementId, InteractiveElement,
|
||||
div, prelude::FluentBuilder as _, px, AnyElement, App, Div, ElementId, InteractiveElement,
|
||||
IntoElement, ParentElement, RenderOnce, ScrollHandle, StatefulInteractiveElement as _, Styled,
|
||||
WindowContext,
|
||||
Window,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Styled for TabBar {
|
||||
}
|
||||
|
||||
impl RenderOnce for TabBar {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
self.base
|
||||
.id(self.id)
|
||||
.group("tab-bar")
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
use crate::scroll::ScrollbarShow;
|
||||
use colors::{default_color_scales, hsl};
|
||||
use gpui::{
|
||||
AppContext, Global, Hsla, ModelContext, SharedString, ViewContext, WindowAppearance,
|
||||
WindowContext,
|
||||
};
|
||||
use gpui::{App, Global, Hsla, SharedString, Window, WindowAppearance};
|
||||
use scale::ColorScaleSet;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub mod colors;
|
||||
pub mod scale;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
Theme::sync_system_appearance(None, cx)
|
||||
}
|
||||
|
||||
pub trait ActiveTheme {
|
||||
fn theme(&self) -> &Theme;
|
||||
}
|
||||
|
||||
impl ActiveTheme for App {
|
||||
#[inline]
|
||||
fn theme(&self) -> &Theme {
|
||||
Theme::global(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct SystemColors {
|
||||
pub background: Hsla,
|
||||
@@ -47,38 +59,6 @@ impl SystemColors {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ActiveTheme {
|
||||
fn theme(&self) -> &Theme;
|
||||
}
|
||||
|
||||
impl ActiveTheme for AppContext {
|
||||
fn theme(&self) -> &Theme {
|
||||
Theme::global(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> ActiveTheme for ViewContext<'_, V> {
|
||||
fn theme(&self) -> &Theme {
|
||||
self.deref().theme()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> ActiveTheme for ModelContext<'_, V> {
|
||||
fn theme(&self) -> &Theme {
|
||||
self.deref().theme()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveTheme for WindowContext<'_> {
|
||||
fn theme(&self) -> &Theme {
|
||||
self.deref().theme()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
Theme::sync_system_appearance(cx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq)]
|
||||
pub enum Appearance {
|
||||
#[default]
|
||||
@@ -126,32 +106,34 @@ impl Global for Theme {}
|
||||
|
||||
impl Theme {
|
||||
/// Returns the global theme reference
|
||||
pub fn global(cx: &AppContext) -> &Theme {
|
||||
pub fn global(cx: &App) -> &Theme {
|
||||
cx.global::<Theme>()
|
||||
}
|
||||
|
||||
/// Returns the global theme mutable reference
|
||||
pub fn global_mut(cx: &mut AppContext) -> &mut Theme {
|
||||
pub fn global_mut(cx: &mut App) -> &mut Theme {
|
||||
cx.global_mut::<Theme>()
|
||||
}
|
||||
|
||||
/// Sync the theme with the system appearance
|
||||
pub fn sync_system_appearance(cx: &mut AppContext) {
|
||||
pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
|
||||
match cx.window_appearance() {
|
||||
WindowAppearance::Dark | WindowAppearance::VibrantDark => {
|
||||
Self::change(Appearance::Dark, cx)
|
||||
Self::change(Appearance::Dark, window, cx)
|
||||
}
|
||||
WindowAppearance::Light | WindowAppearance::VibrantLight => {
|
||||
Self::change(Appearance::Light, cx)
|
||||
Self::change(Appearance::Light, window, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change(mode: Appearance, cx: &mut AppContext) {
|
||||
pub fn change(mode: Appearance, window: Option<&mut Window>, cx: &mut App) {
|
||||
let theme = Theme::new(mode);
|
||||
|
||||
cx.set_global(theme);
|
||||
cx.refresh();
|
||||
|
||||
if let Some(window) = window {
|
||||
window.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::theme::{ActiveTheme, Appearance};
|
||||
use gpui::{AppContext, Hsla, SharedString};
|
||||
use gpui::{App, Hsla, SharedString};
|
||||
|
||||
/// A collection of colors that are used to style the UI.
|
||||
///
|
||||
@@ -279,21 +279,21 @@ impl ColorScaleSet {
|
||||
&self.dark_alpha
|
||||
}
|
||||
|
||||
pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla {
|
||||
pub fn step(&self, cx: &App, step: ColorScaleStep) -> Hsla {
|
||||
match cx.theme().appearance {
|
||||
Appearance::Light => self.light().step(step),
|
||||
Appearance::Dark => self.dark().step(step),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla {
|
||||
pub fn step_alpha(&self, cx: &App, step: ColorScaleStep) -> Hsla {
|
||||
match cx.theme().appearance {
|
||||
Appearance::Light => self.light_alpha.step(step),
|
||||
Appearance::Dark => self.dark_alpha.step(step),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn darken(&self, cx: &AppContext) -> Hsla {
|
||||
pub fn darken(&self, cx: &App) -> Hsla {
|
||||
match cx.theme().appearance {
|
||||
Appearance::Light => self.light.step_12(),
|
||||
Appearance::Dark => self.dark.step_1(),
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::{
|
||||
Icon, IconName, InteractiveElementExt as _, Sizable as _,
|
||||
};
|
||||
use gpui::{
|
||||
black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, ClickEvent, Div,
|
||||
black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, App, ClickEvent, Div,
|
||||
Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
|
||||
RenderOnce, Rgba, Stateful, StatefulInteractiveElement as _, Style, Styled, WindowContext,
|
||||
RenderOnce, Rgba, Stateful, StatefulInteractiveElement as _, Style, Styled, Window,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -17,7 +17,7 @@ const TITLE_BAR_LEFT_PADDING: Pixels = px(80.);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const TITLE_BAR_LEFT_PADDING: Pixels = px(12.);
|
||||
|
||||
type OnCloseWindow = Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>;
|
||||
type OnCloseWindow = Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>;
|
||||
|
||||
/// TitleBar used to customize the appearance of the title bar.
|
||||
///
|
||||
@@ -38,11 +38,11 @@ impl TitleBar {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add custom for close window event, default is None, then click X button will call `cx.remove_window()`.
|
||||
/// Add custom for close window event, default is None, then click X button will call `window.remove_window()`.
|
||||
/// Linux only, this will do nothing on other platforms.
|
||||
pub fn on_close_window(
|
||||
mut self,
|
||||
f: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
f: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
if cfg!(target_os = "linux") {
|
||||
self.on_close_window = Some(Rc::new(Box::new(f)));
|
||||
@@ -108,7 +108,7 @@ impl Control {
|
||||
matches!(self, Self::Close { .. })
|
||||
}
|
||||
|
||||
fn fg(&self, cx: &WindowContext) -> Hsla {
|
||||
fn fg(&self, _window: &Window, cx: &App) -> Hsla {
|
||||
if cx.theme().appearance.is_dark() {
|
||||
white()
|
||||
} else {
|
||||
@@ -116,7 +116,7 @@ impl Control {
|
||||
}
|
||||
}
|
||||
|
||||
fn hover_fg(&self, cx: &WindowContext) -> Hsla {
|
||||
fn hover_fg(&self, _window: &Window, cx: &App) -> Hsla {
|
||||
if self.is_close() || cx.theme().appearance.is_dark() {
|
||||
white()
|
||||
} else {
|
||||
@@ -124,7 +124,7 @@ impl Control {
|
||||
}
|
||||
}
|
||||
|
||||
fn hover_bg(&self, cx: &WindowContext) -> Rgba {
|
||||
fn hover_bg(&self, _window: &Window, cx: &App) -> Rgba {
|
||||
if self.is_close() {
|
||||
Rgba {
|
||||
r: 232.0 / 255.0,
|
||||
@@ -151,10 +151,10 @@ impl Control {
|
||||
}
|
||||
|
||||
impl RenderOnce for Control {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let fg = self.fg(cx);
|
||||
let hover_fg = self.hover_fg(cx);
|
||||
let hover_bg = self.hover_bg(cx);
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let fg = self.fg(window, cx);
|
||||
let hover_fg = self.hover_fg(window, cx);
|
||||
let hover_bg = self.hover_bg(window, cx);
|
||||
let icon = self.clone();
|
||||
let is_linux = cfg!(target_os = "linux");
|
||||
let on_close_window = match &icon {
|
||||
@@ -173,19 +173,19 @@ impl RenderOnce for Control {
|
||||
.items_center()
|
||||
.text_color(fg)
|
||||
.when(is_linux, |this| {
|
||||
this.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||
cx.prevent_default();
|
||||
this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.prevent_default();
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_click(move |_, cx| match icon {
|
||||
Self::Minimize => cx.minimize_window(),
|
||||
Self::Restore => cx.zoom_window(),
|
||||
Self::Maximize => cx.zoom_window(),
|
||||
.on_click(move |_, window, cx| match icon {
|
||||
Self::Minimize => window.minimize_window(),
|
||||
Self::Restore => window.zoom_window(),
|
||||
Self::Maximize => window.zoom_window(),
|
||||
Self::Close { .. } => {
|
||||
if let Some(f) = on_close_window.clone() {
|
||||
f(&ClickEvent::default(), cx);
|
||||
f(&ClickEvent::default(), window, cx);
|
||||
} else {
|
||||
cx.remove_window();
|
||||
window.remove_window();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -202,7 +202,7 @@ struct WindowControls {
|
||||
}
|
||||
|
||||
impl RenderOnce for WindowControls {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
if cfg!(target_os = "macos") {
|
||||
return div().id("window-controls");
|
||||
}
|
||||
@@ -218,7 +218,7 @@ impl RenderOnce for WindowControls {
|
||||
.content_stretch()
|
||||
.h_full()
|
||||
.child(Control::minimize())
|
||||
.child(if cx.is_maximized() {
|
||||
.child(if window.is_maximized() {
|
||||
Control::restore()
|
||||
} else {
|
||||
Control::maximize()
|
||||
@@ -241,7 +241,7 @@ impl ParentElement for TitleBar {
|
||||
}
|
||||
|
||||
impl RenderOnce for TitleBar {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let is_linux = cfg!(target_os = "linux");
|
||||
|
||||
div().flex_shrink_0().child(
|
||||
@@ -252,8 +252,8 @@ impl RenderOnce for TitleBar {
|
||||
.justify_between()
|
||||
.h(HEIGHT)
|
||||
.bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
|
||||
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
||||
.on_double_click(|_, cx| cx.zoom_window())
|
||||
.when(window.is_fullscreen(), |this| this.pl(px(12.)))
|
||||
.on_double_click(|_, window, _cx| window.zoom_window())
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
@@ -302,7 +302,8 @@ impl Element for TitleBarElement {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let style = Style {
|
||||
flex_grow: 1.0,
|
||||
@@ -314,7 +315,7 @@ impl Element for TitleBarElement {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let id = cx.request_layout(style, []);
|
||||
let id = window.request_layout(style, [], cx);
|
||||
|
||||
(id, ())
|
||||
}
|
||||
@@ -324,7 +325,8 @@ impl Element for TitleBarElement {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut WindowContext,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
}
|
||||
|
||||
@@ -334,20 +336,25 @@ impl Element for TitleBarElement {
|
||||
bounds: gpui::Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) {
|
||||
use gpui::{MouseButton, MouseMoveEvent, MouseUpEvent};
|
||||
|
||||
cx.on_mouse_event(move |ev: &MouseMoveEvent, _, cx: &mut WindowContext| {
|
||||
if bounds.contains(&ev.position) && ev.pressed_button == Some(MouseButton::Left) {
|
||||
cx.start_window_move();
|
||||
}
|
||||
});
|
||||
window.on_mouse_event(
|
||||
move |ev: &MouseMoveEvent, _, window: &mut Window, _cx: &mut App| {
|
||||
if bounds.contains(&ev.position) && ev.pressed_button == Some(MouseButton::Left) {
|
||||
window.start_window_move();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cx.on_mouse_event(move |ev: &MouseUpEvent, _, cx: &mut WindowContext| {
|
||||
if ev.button == MouseButton::Left {
|
||||
cx.show_window_menu(ev.position);
|
||||
}
|
||||
});
|
||||
window.on_mouse_event(
|
||||
move |ev: &MouseUpEvent, _, window: &mut Window, _cx: &mut App| {
|
||||
if ev.button == MouseButton::Left {
|
||||
window.show_window_menu(ev.position);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
|
||||
use gpui::{
|
||||
div, px, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
div, px, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString,
|
||||
Styled, Window,
|
||||
};
|
||||
|
||||
pub struct Tooltip {
|
||||
@@ -9,13 +9,13 @@ pub struct Tooltip {
|
||||
}
|
||||
|
||||
impl Tooltip {
|
||||
pub fn new(text: impl Into<SharedString>, cx: &mut WindowContext) -> View<Self> {
|
||||
cx.new_view(|_| Self { text: text.into() })
|
||||
pub fn new(text: impl Into<SharedString>, _window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|_| Self { text: text.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Tooltip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().child(
|
||||
// Wrap in a child, to ensure the left margin is applied to the tooltip
|
||||
div()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::theme::ActiveTheme;
|
||||
use gpui::{
|
||||
canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, Bounds, CursorStyle,
|
||||
canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, CursorStyle,
|
||||
Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement,
|
||||
Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, WindowContext,
|
||||
Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, Window,
|
||||
};
|
||||
|
||||
pub(crate) const BORDER_SIZE: Pixels = Pixels(1.0);
|
||||
@@ -24,8 +24,8 @@ pub struct WindowBorder {
|
||||
}
|
||||
|
||||
/// Get the window paddings.
|
||||
pub fn window_paddings(cx: &WindowContext) -> Edges<Pixels> {
|
||||
match cx.window_decorations() {
|
||||
pub fn window_paddings(window: &Window, _cx: &App) -> Edges<Pixels> {
|
||||
match window.window_decorations() {
|
||||
Decorations::Server => Edges::all(px(0.0)),
|
||||
Decorations::Client { tiling } => {
|
||||
let mut paddings = Edges::all(SHADOW_SIZE);
|
||||
@@ -61,9 +61,9 @@ impl ParentElement for WindowBorder {
|
||||
}
|
||||
|
||||
impl RenderOnce for WindowBorder {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let decorations = cx.window_decorations();
|
||||
cx.set_client_inset(SHADOW_SIZE);
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let decorations = window.window_decorations();
|
||||
window.set_client_inset(SHADOW_SIZE);
|
||||
|
||||
div()
|
||||
.id("window-backdrop")
|
||||
@@ -74,22 +74,22 @@ impl RenderOnce for WindowBorder {
|
||||
.bg(gpui::transparent_black())
|
||||
.child(
|
||||
canvas(
|
||||
|_bounds, cx| {
|
||||
cx.insert_hitbox(
|
||||
|_bounds, window, _cx| {
|
||||
window.insert_hitbox(
|
||||
Bounds::new(
|
||||
point(px(0.0), px(0.0)),
|
||||
cx.window_bounds().get_bounds().size,
|
||||
window.window_bounds().get_bounds().size,
|
||||
),
|
||||
false,
|
||||
)
|
||||
},
|
||||
move |_bounds, hitbox, cx| {
|
||||
let mouse = cx.mouse_position();
|
||||
let size = cx.window_bounds().get_bounds().size;
|
||||
move |_bounds, hitbox, window, _cx| {
|
||||
let mouse = window.mouse_position();
|
||||
let size = window.window_bounds().get_bounds().size;
|
||||
let Some(edge) = resize_edge(mouse, SHADOW_SIZE, size) else {
|
||||
return;
|
||||
};
|
||||
cx.set_cursor_style(
|
||||
window.set_cursor_style(
|
||||
match edge {
|
||||
ResizeEdge::Top | ResizeEdge::Bottom => {
|
||||
CursorStyle::ResizeUpDown
|
||||
@@ -121,13 +121,13 @@ impl RenderOnce for WindowBorder {
|
||||
.when(!tiling.bottom, |div| div.pb(SHADOW_SIZE))
|
||||
.when(!tiling.left, |div| div.pl(SHADOW_SIZE))
|
||||
.when(!tiling.right, |div| div.pr(SHADOW_SIZE))
|
||||
.on_mouse_move(|_e, cx| cx.refresh())
|
||||
.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||
let size = cx.window_bounds().get_bounds().size;
|
||||
let pos = cx.mouse_position();
|
||||
.on_mouse_move(|_e, window, _cx| window.refresh())
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, _cx| {
|
||||
let size = window.window_bounds().get_bounds().size;
|
||||
let pos = window.mouse_position();
|
||||
|
||||
if let Some(edge) = resize_edge(pos, SHADOW_SIZE, size) {
|
||||
cx.start_window_resize(edge)
|
||||
window.start_window_resize(edge)
|
||||
};
|
||||
}),
|
||||
})
|
||||
@@ -162,7 +162,7 @@ impl RenderOnce for WindowBorder {
|
||||
}])
|
||||
}),
|
||||
})
|
||||
.on_mouse_move(|_e, cx| {
|
||||
.on_mouse_move(|_e, _window, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.bg(gpui::transparent_black())
|
||||
|
||||
Reference in New Issue
Block a user