chore: Upgrade to GPUI3 (#6)

* wip: gpui3

* wip: gpui3

* chore: fix clippy
This commit is contained in:
reya
2025-01-28 08:25:49 +07:00
committed by GitHub
parent 3c15e74e56
commit 72a6d79bc5
62 changed files with 2572 additions and 2511 deletions

View File

@@ -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 {

View File

@@ -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);
})
},
)

View File

@@ -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);
})
},
)

View File

@@ -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)
}
}

View File

@@ -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();
}
});
},

View File

@@ -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()

View File

@@ -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));
}
}
})

View File

@@ -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()
))
}
}

View File

@@ -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| {

View File

@@ -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));
}

View File

@@ -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()

View File

@@ -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)
}
}
}
}

View File

@@ -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))
}
}

View File

@@ -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);
})),
),
)

View File

@@ -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> {}

View File

@@ -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();
}
}

View File

@@ -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));

View File

@@ -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

View File

@@ -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;

View File

@@ -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()
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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::*;

View File

@@ -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);

View File

@@ -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();
}))

View File

@@ -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)))
}
}

View File

@@ -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);
},
),
)

View File

@@ -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(&notification, 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),

View File

@@ -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();
}
});
},
);
}
}

View File

@@ -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 {

View File

@@ -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,
};

View File

@@ -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.,

View File

@@ -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);
})
},
)

View File

@@ -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 {

View File

@@ -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));
}
}
})

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
});

View File

@@ -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))

View File

@@ -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))

View File

@@ -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)
}
}

View File

@@ -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),

View File

@@ -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")

View File

@@ -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();
}
}
}

View File

@@ -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(),

View File

@@ -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);
}
},
);
}
}

View File

@@ -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()

View File

@@ -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())