feat: update gpui-components
This commit is contained in:
@@ -1,247 +0,0 @@
|
|||||||
use std::{rc::Rc, time::Duration};
|
|
||||||
|
|
||||||
use gpui::{
|
|
||||||
actions, anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _,
|
|
||||||
AnyElement, AppContext, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter,
|
|
||||||
FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement,
|
|
||||||
Pixels, RenderOnce, Styled, WindowContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
button::{Button, ButtonVariants as _},
|
|
||||||
h_flex,
|
|
||||||
modal::overlay_color,
|
|
||||||
root::ContextModal as _,
|
|
||||||
scroll::ScrollbarAxis,
|
|
||||||
theme::ActiveTheme,
|
|
||||||
title_bar::TITLE_BAR_HEIGHT,
|
|
||||||
v_flex, IconName, Placement, Sizable, StyledExt as _,
|
|
||||||
};
|
|
||||||
|
|
||||||
actions!(drawer, [Escape]);
|
|
||||||
|
|
||||||
const CONTEXT: &str = "Drawer";
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
|
||||||
}
|
|
||||||
|
|
||||||
type OnClose = Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct Drawer {
|
|
||||||
pub(crate) focus_handle: FocusHandle,
|
|
||||||
placement: Placement,
|
|
||||||
size: DefiniteLength,
|
|
||||||
resizable: bool,
|
|
||||||
on_close: OnClose,
|
|
||||||
title: Option<AnyElement>,
|
|
||||||
footer: Option<AnyElement>,
|
|
||||||
content: Div,
|
|
||||||
margin_top: Pixels,
|
|
||||||
overlay: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawer {
|
|
||||||
pub fn new(cx: &mut WindowContext) -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
placement: Placement::Right,
|
|
||||||
size: DefiniteLength::Absolute(px(350.).into()),
|
|
||||||
resizable: true,
|
|
||||||
title: None,
|
|
||||||
footer: None,
|
|
||||||
content: v_flex().px_4().py_3(),
|
|
||||||
margin_top: TITLE_BAR_HEIGHT,
|
|
||||||
overlay: true,
|
|
||||||
on_close: Rc::new(|_, _| {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the title of the drawer.
|
|
||||||
pub fn title(mut self, title: impl IntoElement) -> Self {
|
|
||||||
self.title = Some(title.into_any_element());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the footer of the drawer.
|
|
||||||
pub fn footer(mut self, footer: impl IntoElement) -> Self {
|
|
||||||
self.footer = Some(footer.into_any_element());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the size of the drawer, default is 350px.
|
|
||||||
pub fn size(mut self, size: impl Into<DefiniteLength>) -> Self {
|
|
||||||
self.size = size.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the margin top of the drawer, default is 0px.
|
|
||||||
///
|
|
||||||
/// This is used to let Drawer be placed below a Windows Title, you can give the height of the title bar.
|
|
||||||
pub fn margin_top(mut self, top: Pixels) -> Self {
|
|
||||||
self.margin_top = top;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the placement of the drawer, default is `Placement::Right`.
|
|
||||||
pub fn placement(mut self, placement: Placement) -> Self {
|
|
||||||
self.placement = placement;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the placement of the drawer, default is `Placement::Right`.
|
|
||||||
pub fn set_placement(&mut self, placement: Placement) {
|
|
||||||
self.placement = placement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether the drawer is resizable, default is `true`.
|
|
||||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
|
||||||
self.resizable = resizable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set whether the drawer should have an overlay, default is `true`.
|
|
||||||
pub fn overlay(mut self, overlay: bool) -> Self {
|
|
||||||
self.overlay = overlay;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Listen to the close event of the drawer.
|
|
||||||
pub fn on_close(
|
|
||||||
mut self,
|
|
||||||
on_close: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.on_close = Rc::new(on_close);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for Drawer {}
|
|
||||||
impl ParentElement for Drawer {
|
|
||||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
|
||||||
self.content.extend(elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Styled for Drawer {
|
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
|
||||||
self.content.style()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for Drawer {
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let placement = self.placement;
|
|
||||||
let titlebar_height = self.margin_top;
|
|
||||||
let size = cx.viewport_size();
|
|
||||||
let on_close = self.on_close.clone();
|
|
||||||
|
|
||||||
anchored()
|
|
||||||
.position(point(px(0.), titlebar_height))
|
|
||||||
.snap_to_window()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.occlude()
|
|
||||||
.w(size.width)
|
|
||||||
.h(size.height - titlebar_height)
|
|
||||||
.bg(overlay_color(self.overlay, cx))
|
|
||||||
.when(self.overlay, |this| {
|
|
||||||
this.on_mouse_down(MouseButton::Left, {
|
|
||||||
let on_close = self.on_close.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
on_close(&ClickEvent::default(), cx);
|
|
||||||
cx.close_drawer();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.id("drawer")
|
|
||||||
.key_context(CONTEXT)
|
|
||||||
.track_focus(&self.focus_handle)
|
|
||||||
.on_action({
|
|
||||||
let on_close = self.on_close.clone();
|
|
||||||
move |_: &Escape, cx| {
|
|
||||||
on_close(&ClickEvent::default(), cx);
|
|
||||||
cx.close_drawer();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.absolute()
|
|
||||||
.occlude()
|
|
||||||
.bg(cx.theme().background)
|
|
||||||
.border_color(cx.theme().border)
|
|
||||||
.shadow_xl()
|
|
||||||
.map(|this| {
|
|
||||||
// Set the size of the drawer.
|
|
||||||
if placement.is_horizontal() {
|
|
||||||
this.h_full().w(self.size)
|
|
||||||
} else {
|
|
||||||
this.w_full().h(self.size)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|this| match self.placement {
|
|
||||||
Placement::Top => this.top_0().left_0().right_0().border_b_1(),
|
|
||||||
Placement::Right => this.top_0().right_0().bottom_0().border_l_1(),
|
|
||||||
Placement::Bottom => {
|
|
||||||
this.bottom_0().left_0().right_0().border_t_1()
|
|
||||||
}
|
|
||||||
Placement::Left => this.top_0().left_0().bottom_0().border_r_1(),
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
// TitleBar
|
|
||||||
h_flex()
|
|
||||||
.justify_between()
|
|
||||||
.px_4()
|
|
||||||
.py_2()
|
|
||||||
.w_full()
|
|
||||||
.child(self.title.unwrap_or(div().into_any_element()))
|
|
||||||
.child(
|
|
||||||
Button::new("close")
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.icon(IconName::Close)
|
|
||||||
.on_click(move |_, cx| {
|
|
||||||
on_close(&ClickEvent::default(), cx);
|
|
||||||
cx.close_drawer();
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
// Body
|
|
||||||
div().flex_1().overflow_hidden().child(
|
|
||||||
v_flex()
|
|
||||||
.scrollable(
|
|
||||||
cx.parent_view_id().unwrap_or_default(),
|
|
||||||
ScrollbarAxis::Vertical,
|
|
||||||
)
|
|
||||||
.child(self.content),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.when_some(self.footer, |this, footer| {
|
|
||||||
// Footer
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.justify_between()
|
|
||||||
.px_4()
|
|
||||||
.py_3()
|
|
||||||
.w_full()
|
|
||||||
.child(footer),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_animation(
|
|
||||||
"slide",
|
|
||||||
Animation::new(Duration::from_secs_f64(0.15)),
|
|
||||||
move |this, delta| {
|
|
||||||
let y = px(-100.) + delta * px(100.);
|
|
||||||
this.map(|this| match placement {
|
|
||||||
Placement::Top => this.top(y),
|
|
||||||
Placement::Right => this.right(y),
|
|
||||||
Placement::Bottom => this.bottom(y),
|
|
||||||
Placement::Left => this.left(y),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
mod colors;
|
|
||||||
mod event;
|
|
||||||
mod focusable;
|
|
||||||
mod icon;
|
|
||||||
mod root;
|
|
||||||
mod styled;
|
|
||||||
mod title_bar;
|
|
||||||
|
|
||||||
pub mod accordion;
|
pub mod accordion;
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
pub mod badge;
|
pub mod badge;
|
||||||
@@ -18,7 +10,6 @@ pub mod color_picker;
|
|||||||
pub mod context_menu;
|
pub mod context_menu;
|
||||||
pub mod divider;
|
pub mod divider;
|
||||||
pub mod dock;
|
pub mod dock;
|
||||||
pub mod drawer;
|
|
||||||
pub mod dropdown;
|
pub mod dropdown;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub mod indicator;
|
pub mod indicator;
|
||||||
@@ -45,14 +36,24 @@ pub mod theme;
|
|||||||
pub mod tooltip;
|
pub mod tooltip;
|
||||||
|
|
||||||
pub use crate::Disableable;
|
pub use crate::Disableable;
|
||||||
|
|
||||||
|
pub use colors::*;
|
||||||
pub use event::InteractiveElementExt;
|
pub use event::InteractiveElementExt;
|
||||||
pub use focusable::FocusableCycle;
|
pub use focusable::FocusableCycle;
|
||||||
|
pub use icon::*;
|
||||||
pub use root::{ContextModal, Root};
|
pub use root::{ContextModal, Root};
|
||||||
pub use styled::*;
|
pub use styled::*;
|
||||||
pub use title_bar::*;
|
pub use title_bar::*;
|
||||||
|
pub use window_border::{window_border, WindowBorder};
|
||||||
|
|
||||||
pub use colors::*;
|
mod colors;
|
||||||
pub use icon::*;
|
mod event;
|
||||||
|
mod focusable;
|
||||||
|
mod icon;
|
||||||
|
mod root;
|
||||||
|
mod styled;
|
||||||
|
mod title_bar;
|
||||||
|
mod window_border;
|
||||||
|
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
@@ -67,7 +68,6 @@ pub struct Assets;
|
|||||||
pub fn init(cx: &mut gpui::AppContext) {
|
pub fn init(cx: &mut gpui::AppContext) {
|
||||||
theme::init(cx);
|
theme::init(cx);
|
||||||
dock::init(cx);
|
dock::init(cx);
|
||||||
drawer::init(cx);
|
|
||||||
dropdown::init(cx);
|
dropdown::init(cx);
|
||||||
input::init(cx);
|
input::init(cx);
|
||||||
number_input::init(cx);
|
number_input::init(cx);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::{rc::Rc, time::Duration};
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, div, hsla, prelude::FluentBuilder, px, relative, Animation,
|
actions, anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation,
|
||||||
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle, Hsla,
|
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle, Hsla,
|
||||||
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
||||||
RenderOnce, SharedString, Styled, WindowContext,
|
RenderOnce, SharedString, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
animation::cubic_bezier,
|
animation::cubic_bezier,
|
||||||
@@ -37,7 +36,6 @@ pub struct Modal {
|
|||||||
show_close: bool,
|
show_close: bool,
|
||||||
overlay: bool,
|
overlay: bool,
|
||||||
keyboard: bool,
|
keyboard: bool,
|
||||||
|
|
||||||
/// This will be change when open the modal, the focus handle is create when open the modal.
|
/// This will be change when open the modal, the focus handle is create when open the modal.
|
||||||
pub(crate) focus_handle: FocusHandle,
|
pub(crate) focus_handle: FocusHandle,
|
||||||
pub(crate) layer_ix: usize,
|
pub(crate) layer_ix: usize,
|
||||||
@@ -164,7 +162,12 @@ impl RenderOnce for Modal {
|
|||||||
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement {
|
||||||
let layer_ix = self.layer_ix;
|
let layer_ix = self.layer_ix;
|
||||||
let on_close = self.on_close.clone();
|
let on_close = self.on_close.clone();
|
||||||
let view_size = cx.viewport_size();
|
let window_paddings = crate::window_border::window_paddings(cx);
|
||||||
|
let view_size = cx.viewport_size()
|
||||||
|
- gpui::size(
|
||||||
|
window_paddings.left + window_paddings.right,
|
||||||
|
window_paddings.top + window_paddings.bottom,
|
||||||
|
);
|
||||||
let bounds = Bounds {
|
let bounds = Bounds {
|
||||||
origin: Point::default(),
|
origin: Point::default(),
|
||||||
size: view_size,
|
size: view_size,
|
||||||
@@ -173,78 +176,83 @@ impl RenderOnce for Modal {
|
|||||||
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
||||||
let x = bounds.center().x - self.width / 2.;
|
let x = bounds.center().x - self.width / 2.;
|
||||||
|
|
||||||
anchored().snap_to_window().child(
|
anchored()
|
||||||
div()
|
.position(point(window_paddings.left, window_paddings.top))
|
||||||
.occlude()
|
.snap_to_window()
|
||||||
.w(view_size.width)
|
.child(
|
||||||
.h(view_size.height)
|
div()
|
||||||
.when(self.overlay_visible, |this| {
|
.occlude()
|
||||||
this.bg(overlay_color(self.overlay, cx))
|
.w(view_size.width)
|
||||||
})
|
.h(view_size.height)
|
||||||
.when(self.overlay, |this| {
|
.when(self.overlay_visible, |this| {
|
||||||
this.on_mouse_down(MouseButton::Left, {
|
this.bg(overlay_color(self.overlay, cx))
|
||||||
|
})
|
||||||
|
.on_mouse_down(MouseButton::Left, {
|
||||||
let on_close = self.on_close.clone();
|
let on_close = self.on_close.clone();
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
on_close(&ClickEvent::default(), cx);
|
on_close(&ClickEvent::default(), cx);
|
||||||
cx.close_modal();
|
cx.close_modal();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
.child(
|
||||||
.child(
|
self.base
|
||||||
self.base
|
.id(SharedString::from(format!("modal-{layer_ix}")))
|
||||||
.id(SharedString::from(format!("modal-{layer_ix}")))
|
.key_context(CONTEXT)
|
||||||
.key_context(CONTEXT)
|
.track_focus(&self.focus_handle)
|
||||||
.track_focus(&self.focus_handle)
|
.when(self.keyboard, |this| {
|
||||||
.when(self.keyboard, |this| {
|
this.on_action({
|
||||||
this.on_action({
|
let on_close = self.on_close.clone();
|
||||||
let on_close = self.on_close.clone();
|
move |_: &Escape, cx| {
|
||||||
move |_: &Escape, cx| {
|
// FIXME:
|
||||||
// FIXME:
|
//
|
||||||
//
|
// Here some Modal have no focus_handle, so it will not work will Escape key.
|
||||||
// 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.
|
||||||
// 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);
|
||||||
on_close(&ClickEvent::default(), cx);
|
cx.close_modal();
|
||||||
cx.close_modal();
|
}
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
})
|
.absolute()
|
||||||
.absolute()
|
.occlude()
|
||||||
.occlude()
|
.relative()
|
||||||
.relative()
|
.left(x)
|
||||||
.left(x)
|
.top(y)
|
||||||
.top(y)
|
.w(self.width)
|
||||||
.w(self.width)
|
.when_some(self.max_width, |this, w| this.max_w(w))
|
||||||
.when_some(self.max_width, |this, w| this.max_w(w))
|
.when_some(self.title, |this, title| {
|
||||||
.when_some(self.title, |this, title| {
|
this.child(div().line_height(relative(1.)).child(title))
|
||||||
this.child(div().line_height(relative(1.)).child(title))
|
})
|
||||||
})
|
.when(self.show_close, |this| {
|
||||||
.when(self.show_close, |this| {
|
this.child(
|
||||||
this.child(
|
Button::new(SharedString::from(format!(
|
||||||
Button::new(SharedString::from(format!("modal-close-{layer_ix}")))
|
"modal-close-{layer_ix}"
|
||||||
|
)))
|
||||||
.absolute()
|
.absolute()
|
||||||
.top_2()
|
.top_2()
|
||||||
.right_2()
|
.right_2()
|
||||||
.small()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.icon(IconName::Close)
|
.icon(IconName::Close)
|
||||||
.on_click(move |_, cx| {
|
.on_click(
|
||||||
on_close(&ClickEvent::default(), cx);
|
move |_, cx| {
|
||||||
cx.close_modal();
|
on_close(&ClickEvent::default(), cx);
|
||||||
}),
|
cx.close_modal();
|
||||||
)
|
},
|
||||||
})
|
),
|
||||||
.child(self.content)
|
)
|
||||||
.children(self.footer)
|
})
|
||||||
.with_animation(
|
.child(self.content)
|
||||||
"slide-down",
|
.children(self.footer)
|
||||||
Animation::new(Duration::from_secs_f64(0.25))
|
.with_animation(
|
||||||
.with_easing(cubic_bezier(0.32, 0.72, 0., 1.)),
|
"slide-down",
|
||||||
move |this, delta| {
|
Animation::new(Duration::from_secs_f64(0.25))
|
||||||
let y_offset = px(0.) + delta * px(30.);
|
.with_easing(cubic_bezier(0.32, 0.72, 0., 1.)),
|
||||||
this.top(y + y_offset).opacity(delta)
|
move |this, delta| {
|
||||||
},
|
let y_offset = px(0.) + delta * px(30.);
|
||||||
),
|
this.top(y + y_offset).opacity(delta)
|
||||||
),
|
},
|
||||||
)
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
use std::cell::Cell;
|
use gpui::*;
|
||||||
use std::ops::Deref;
|
use prelude::FluentBuilder;
|
||||||
use std::rc::Rc;
|
use std::{cell::Cell, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
|
||||||
actions, div, prelude::FluentBuilder, px, Action, AppContext, DismissEvent, EventEmitter,
|
|
||||||
FocusHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render,
|
|
||||||
SharedString, View, ViewContext, VisualContext as _, WindowContext,
|
|
||||||
};
|
|
||||||
use gpui::{
|
|
||||||
anchored, canvas, rems, AnyElement, Bounds, Corner, Edges, FocusableView, Keystroke,
|
|
||||||
ScrollHandle, StatefulInteractiveElement, Styled, WeakView,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::scroll::{Scrollbar, ScrollbarState};
|
use crate::scroll::{Scrollbar, ScrollbarState};
|
||||||
use crate::StyledExt;
|
use crate::StyledExt;
|
||||||
@@ -386,9 +376,10 @@ impl PopupMenu {
|
|||||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||||
let count = self.clickable_menu_items().count();
|
let count = self.clickable_menu_items().count();
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
|
let last_ix = count.saturating_sub(1);
|
||||||
let ix = self
|
let ix = self
|
||||||
.selected_index
|
.selected_index
|
||||||
.map(|index| if index == count - 1 { 0 } else { index + 1 })
|
.map(|index| if index == last_ix { 0 } else { index + 1 })
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
self.selected_index = Some(ix);
|
self.selected_index = Some(ix);
|
||||||
@@ -399,10 +390,18 @@ impl PopupMenu {
|
|||||||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||||
let count = self.clickable_menu_items().count();
|
let count = self.clickable_menu_items().count();
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
|
let last_ix = count.saturating_sub(1);
|
||||||
|
|
||||||
let ix = self
|
let ix = self
|
||||||
.selected_index
|
.selected_index
|
||||||
.map(|index| if index == count - 1 { 0 } else { index - 1 })
|
.map(|index| {
|
||||||
.unwrap_or(count - 1);
|
if index == last_ix {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
index.saturating_sub(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(last_ix);
|
||||||
self.selected_index = Some(ix);
|
self.selected_index = Some(ix);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -473,7 +472,9 @@ impl PopupMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FluentBuilder for PopupMenu {}
|
impl FluentBuilder for PopupMenu {}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for PopupMenu {}
|
impl EventEmitter<DismissEvent> for PopupMenu {}
|
||||||
|
|
||||||
impl FocusableView for PopupMenu {
|
impl FocusableView for PopupMenu {
|
||||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
use crate::{
|
|
||||||
drawer::Drawer,
|
|
||||||
modal::Modal,
|
|
||||||
notification::{Notification, NotificationList},
|
|
||||||
theme::ActiveTheme,
|
|
||||||
};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled,
|
div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled,
|
||||||
View, ViewContext, VisualContext as _, WindowContext,
|
View, ViewContext, VisualContext as _, WindowContext,
|
||||||
@@ -13,19 +7,15 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
modal::Modal,
|
||||||
|
notification::{Notification, NotificationList},
|
||||||
|
theme::ActiveTheme,
|
||||||
|
window_border,
|
||||||
|
};
|
||||||
|
|
||||||
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
|
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
|
||||||
pub trait ContextModal: Sized {
|
pub trait ContextModal: Sized {
|
||||||
/// Opens a Drawer.
|
|
||||||
fn open_drawer<F>(&mut self, build: F)
|
|
||||||
where
|
|
||||||
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static;
|
|
||||||
|
|
||||||
/// Return true, if there is an active Drawer.
|
|
||||||
fn has_active_drawer(&self) -> bool;
|
|
||||||
|
|
||||||
/// Closes the active Drawer.
|
|
||||||
fn close_drawer(&mut self);
|
|
||||||
|
|
||||||
/// Opens a Modal.
|
/// Opens a Modal.
|
||||||
fn open_modal<F>(&mut self, build: F)
|
fn open_modal<F>(&mut self, build: F)
|
||||||
where
|
where
|
||||||
@@ -48,38 +38,6 @@ pub trait ContextModal: Sized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContextModal for WindowContext<'_> {
|
impl ContextModal for WindowContext<'_> {
|
||||||
fn open_drawer<F>(&mut self, build: F)
|
|
||||||
where
|
|
||||||
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static,
|
|
||||||
{
|
|
||||||
Root::update(self, move |root, cx| {
|
|
||||||
if root.active_drawer.is_none() {
|
|
||||||
root.previous_focus_handle = cx.focused();
|
|
||||||
}
|
|
||||||
|
|
||||||
let focus_handle = cx.focus_handle();
|
|
||||||
focus_handle.focus(cx);
|
|
||||||
|
|
||||||
root.active_drawer = Some(ActiveDrawer {
|
|
||||||
focus_handle,
|
|
||||||
builder: Rc::new(build),
|
|
||||||
});
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_active_drawer(&self) -> bool {
|
|
||||||
Root::read(self).active_drawer.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_drawer(&mut self) {
|
|
||||||
Root::update(self, |root, cx| {
|
|
||||||
root.active_drawer = None;
|
|
||||||
root.focus_back(cx);
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_modal<F>(&mut self, build: F)
|
fn open_modal<F>(&mut self, build: F)
|
||||||
where
|
where
|
||||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
||||||
@@ -149,21 +107,10 @@ impl ContextModal for WindowContext<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<V> ContextModal for ViewContext<'_, V> {
|
impl<V> ContextModal for ViewContext<'_, V> {
|
||||||
fn open_drawer<F>(&mut self, build: F)
|
|
||||||
where
|
|
||||||
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static,
|
|
||||||
{
|
|
||||||
self.deref_mut().open_drawer(build)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_active_modal(&self) -> bool {
|
fn has_active_modal(&self) -> bool {
|
||||||
self.deref().has_active_modal()
|
self.deref().has_active_modal()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_drawer(&mut self) {
|
|
||||||
self.deref_mut().close_drawer()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_modal<F>(&mut self, build: F)
|
fn open_modal<F>(&mut self, build: F)
|
||||||
where
|
where
|
||||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
F: Fn(Modal, &mut WindowContext) -> Modal + 'static,
|
||||||
@@ -171,10 +118,6 @@ impl<V> ContextModal for ViewContext<'_, V> {
|
|||||||
self.deref_mut().open_modal(build)
|
self.deref_mut().open_modal(build)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_active_drawer(&self) -> bool {
|
|
||||||
self.deref().has_active_drawer()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Close the last active modal.
|
/// Close the last active modal.
|
||||||
fn close_modal(&mut self) {
|
fn close_modal(&mut self) {
|
||||||
self.deref_mut().close_modal()
|
self.deref_mut().close_modal()
|
||||||
@@ -205,21 +148,13 @@ pub struct Root {
|
|||||||
/// Used to store the focus handle of the previous view.
|
/// Used to store the focus handle of the previous view.
|
||||||
/// When the Modal, Drawer closes, we will focus back to the previous view.
|
/// When the Modal, Drawer closes, we will focus back to the previous view.
|
||||||
previous_focus_handle: Option<FocusHandle>,
|
previous_focus_handle: Option<FocusHandle>,
|
||||||
active_drawer: Option<ActiveDrawer>,
|
|
||||||
active_modals: Vec<ActiveModal>,
|
active_modals: Vec<ActiveModal>,
|
||||||
pub notification: View<NotificationList>,
|
pub notification: View<NotificationList>,
|
||||||
view: AnyView,
|
view: AnyView,
|
||||||
}
|
}
|
||||||
|
|
||||||
type DrawerBuilder = Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>;
|
|
||||||
type ModelBuilder = Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>;
|
type ModelBuilder = Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ActiveDrawer {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
builder: DrawerBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ActiveModal {
|
struct ActiveModal {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
@@ -230,7 +165,6 @@ impl Root {
|
|||||||
pub fn new(view: AnyView, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(view: AnyView, cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
previous_focus_handle: None,
|
previous_focus_handle: None,
|
||||||
active_drawer: None,
|
|
||||||
active_modals: Vec::new(),
|
active_modals: Vec::new(),
|
||||||
notification: cx.new_view(NotificationList::new),
|
notification: cx.new_view(NotificationList::new),
|
||||||
view,
|
view,
|
||||||
@@ -277,25 +211,6 @@ impl Root {
|
|||||||
Some(div().child(root.read(cx).notification.clone()))
|
Some(div().child(root.read(cx).notification.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the Drawer layer.
|
|
||||||
pub fn render_drawer_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`.");
|
|
||||||
|
|
||||||
if let Some(active_drawer) = root.read(cx).active_drawer.clone() {
|
|
||||||
let mut drawer = Drawer::new(cx);
|
|
||||||
drawer = (active_drawer.builder)(drawer, cx);
|
|
||||||
drawer.focus_handle = active_drawer.focus_handle.clone();
|
|
||||||
|
|
||||||
return Some(div().child(drawer));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render the Modal layer.
|
/// Render the Modal layer.
|
||||||
pub fn render_modal_layer(cx: &mut WindowContext) -> Option<impl IntoElement> {
|
pub fn render_modal_layer(cx: &mut WindowContext) -> Option<impl IntoElement> {
|
||||||
let root = cx
|
let root = cx
|
||||||
@@ -347,12 +262,15 @@ impl Render for Root {
|
|||||||
let base_font_size = cx.theme().font_size;
|
let base_font_size = cx.theme().font_size;
|
||||||
cx.set_rem_size(base_font_size);
|
cx.set_rem_size(base_font_size);
|
||||||
|
|
||||||
div()
|
window_border().child(
|
||||||
.id("root")
|
div()
|
||||||
.size_full()
|
.id("root")
|
||||||
.font_family(".SystemUIFont")
|
.relative()
|
||||||
.bg(cx.theme().background)
|
.size_full()
|
||||||
.text_color(cx.theme().foreground)
|
.font_family(".SystemUIFont")
|
||||||
.child(self.view.clone())
|
.bg(cx.theme().background)
|
||||||
|
.text_color(cx.theme().foreground)
|
||||||
|
.child(self.view.clone()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
px, relative, AnyView, Bounds, ContentMask, Corners, Edges, Element, ElementId,
|
px, relative, Axis, Bounds, ContentMask, Corners, Edges, Element, ElementId, EntityId,
|
||||||
GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point,
|
GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point,
|
||||||
Position, ScrollHandle, ScrollWheelEvent, Style, WindowContext,
|
Position, ScrollHandle, ScrollWheelEvent, Size, Style, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The scroll axis direction.
|
use crate::AxisExt;
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ScrollableAxis {
|
|
||||||
/// Horizontal scroll.
|
|
||||||
Horizontal,
|
|
||||||
/// Vertical scroll.
|
|
||||||
Vertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make a scrollable mask element to cover the parent view with the mouse wheel event listening.
|
/// Make a scrollable mask element to cover the parent view with the mouse wheel event listening.
|
||||||
///
|
///
|
||||||
@@ -20,21 +12,17 @@ pub enum ScrollableAxis {
|
|||||||
/// You can use this `scroll_handle` to control what you want to scroll.
|
/// You can use this `scroll_handle` to control what you want to scroll.
|
||||||
/// This is only can handle once axis scrolling.
|
/// This is only can handle once axis scrolling.
|
||||||
pub struct ScrollableMask {
|
pub struct ScrollableMask {
|
||||||
view: AnyView,
|
view_id: EntityId,
|
||||||
axis: ScrollableAxis,
|
axis: Axis,
|
||||||
scroll_handle: ScrollHandle,
|
scroll_handle: ScrollHandle,
|
||||||
debug: Option<Hsla>,
|
debug: Option<Hsla>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollableMask {
|
impl ScrollableMask {
|
||||||
/// Create a new scrollable mask element.
|
/// Create a new scrollable mask element.
|
||||||
pub fn new(
|
pub fn new(view_id: EntityId, axis: Axis, scroll_handle: &ScrollHandle) -> Self {
|
||||||
view: impl Into<AnyView>,
|
|
||||||
axis: ScrollableAxis,
|
|
||||||
scroll_handle: &ScrollHandle,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
view: view.into(),
|
view_id,
|
||||||
scroll_handle: scroll_handle.clone(),
|
scroll_handle: scroll_handle.clone(),
|
||||||
axis,
|
axis,
|
||||||
debug: None,
|
debug: None,
|
||||||
@@ -75,7 +63,7 @@ impl Element for ScrollableMask {
|
|||||||
position: Position::Absolute,
|
position: Position::Absolute,
|
||||||
flex_grow: 1.0,
|
flex_grow: 1.0,
|
||||||
flex_shrink: 1.0,
|
flex_shrink: 1.0,
|
||||||
size: gpui::Size {
|
size: Size {
|
||||||
width: relative(1.).into(),
|
width: relative(1.).into(),
|
||||||
height: relative(1.).into(),
|
height: relative(1.).into(),
|
||||||
},
|
},
|
||||||
@@ -127,32 +115,37 @@ impl Element for ScrollableMask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.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 hitbox = hitbox.clone();
|
||||||
let mouse_position = cx.mouse_position();
|
let mouse_position = cx.mouse_position();
|
||||||
let scroll_handle = self.scroll_handle.clone();
|
let last_offset = scroll_handle.offset();
|
||||||
let old_offset = scroll_handle.offset();
|
|
||||||
let view_id = self.view.entity_id();
|
|
||||||
let is_horizontal = self.axis == ScrollableAxis::Horizontal;
|
|
||||||
|
|
||||||
move |event: &ScrollWheelEvent, phase, cx| {
|
move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
if bounds.contains(&mouse_position) && phase.bubble() && hitbox.is_hovered(cx) {
|
if bounds.contains(&mouse_position) && phase.bubble() && hitbox.is_hovered(cx) {
|
||||||
let delta = event.delta.pixel_delta(line_height);
|
let mut offset = scroll_handle.offset();
|
||||||
|
let mut delta = event.delta.pixel_delta(line_height);
|
||||||
|
|
||||||
if is_horizontal && !delta.x.is_zero() {
|
// Limit for only one way scrolling at same time.
|
||||||
// When is horizontal scroll, move the horizontal scroll handle to make scrolling.
|
// When use MacBook touchpad we may get both x and y delta,
|
||||||
let mut offset = scroll_handle.offset();
|
// only allows the one that more to scroll.
|
||||||
|
if !delta.x.is_zero() && !delta.y.is_zero() {
|
||||||
|
if delta.x.abs() > delta.y.abs() {
|
||||||
|
delta.y = px(0.);
|
||||||
|
} else {
|
||||||
|
delta.x = px(0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_horizontal {
|
||||||
offset.x += delta.x;
|
offset.x += delta.x;
|
||||||
scroll_handle.set_offset(offset);
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
if !is_horizontal && !delta.y.is_zero() {
|
|
||||||
// When is vertical scroll, move the vertical scroll handle to make scrolling.
|
|
||||||
let mut offset = scroll_handle.offset();
|
|
||||||
offset.y += delta.y;
|
offset.y += delta.y;
|
||||||
scroll_handle.set_offset(offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if old_offset != scroll_handle.offset() {
|
if last_offset != offset {
|
||||||
|
scroll_handle.set_offset(offset);
|
||||||
cx.notify(Some(view_id));
|
cx.notify(Some(view_id));
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ pub struct ThemeColor {
|
|||||||
pub accordion_hover: Hsla,
|
pub accordion_hover: Hsla,
|
||||||
pub background: Hsla,
|
pub background: Hsla,
|
||||||
pub border: Hsla,
|
pub border: Hsla,
|
||||||
|
pub window_border: Hsla,
|
||||||
pub card: Hsla,
|
pub card: Hsla,
|
||||||
pub card_foreground: Hsla,
|
pub card_foreground: Hsla,
|
||||||
pub destructive: Hsla,
|
pub destructive: Hsla,
|
||||||
@@ -229,6 +230,7 @@ impl ThemeColor {
|
|||||||
accordion_hover: hsl(240.0, 4.8, 95.9).opacity(0.7),
|
accordion_hover: hsl(240.0, 4.8, 95.9).opacity(0.7),
|
||||||
background: hsl(0.0, 0.0, 100.),
|
background: hsl(0.0, 0.0, 100.),
|
||||||
border: hsl(240.0, 5.9, 90.0),
|
border: hsl(240.0, 5.9, 90.0),
|
||||||
|
window_border: hsl(240.0, 5.9, 78.0),
|
||||||
card: hsl(0.0, 0.0, 100.0),
|
card: hsl(0.0, 0.0, 100.0),
|
||||||
card_foreground: hsl(240.0, 10.0, 3.9),
|
card_foreground: hsl(240.0, 10.0, 3.9),
|
||||||
destructive: hsl(0.0, 84.2, 60.2),
|
destructive: hsl(0.0, 84.2, 60.2),
|
||||||
@@ -304,6 +306,7 @@ impl ThemeColor {
|
|||||||
accordion_hover: hsl(240.0, 3.7, 15.9).opacity(0.7),
|
accordion_hover: hsl(240.0, 3.7, 15.9).opacity(0.7),
|
||||||
background: hsl(0.0, 0.0, 8.0),
|
background: hsl(0.0, 0.0, 8.0),
|
||||||
border: hsl(240.0, 3.7, 16.9),
|
border: hsl(240.0, 3.7, 16.9),
|
||||||
|
window_border: hsl(240.0, 3.7, 28.0),
|
||||||
card: hsl(0.0, 0.0, 8.0),
|
card: hsl(0.0, 0.0, 8.0),
|
||||||
card_foreground: hsl(0.0, 0.0, 78.0),
|
card_foreground: hsl(0.0, 0.0, 78.0),
|
||||||
destructive: hsl(0.0, 62.8, 30.6),
|
destructive: hsl(0.0, 62.8, 30.6),
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
use gpui::{
|
||||||
|
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla,
|
||||||
|
InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, Stateful,
|
||||||
|
StatefulInteractiveElement as _, Style, Styled, WindowContext,
|
||||||
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _};
|
use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _};
|
||||||
use gpui::{
|
|
||||||
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla,
|
|
||||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Stateful,
|
|
||||||
StatefulInteractiveElement as _, Style, Styled, WindowContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
pub const HEIGHT: Pixels = px(34.);
|
||||||
pub const TITLE_BAR_HEIGHT: Pixels = px(35.);
|
pub const TITLE_BAR_HEIGHT: Pixels = px(35.);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
const TITLE_BAR_LEFT_PADDING: Pixels = px(80.);
|
const TITLE_BAR_LEFT_PADDING: Pixels = px(80.);
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
@@ -158,7 +160,11 @@ impl RenderOnce for ControlIcon {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.text_color(fg)
|
.text_color(fg)
|
||||||
.when(is_linux, |this| {
|
.when(is_linux, |this| {
|
||||||
this.on_click(move |_, cx| match icon {
|
this.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||||
|
cx.prevent_default();
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_click(move |_, cx| match icon {
|
||||||
Self::Minimize => cx.minimize_window(),
|
Self::Minimize => cx.minimize_window(),
|
||||||
Self::Restore => cx.zoom_window(),
|
Self::Restore => cx.zoom_window(),
|
||||||
Self::Maximize => cx.zoom_window(),
|
Self::Maximize => cx.zoom_window(),
|
||||||
@@ -225,45 +231,41 @@ impl RenderOnce for TitleBar {
|
|||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let is_linux = cfg!(target_os = "linux");
|
let is_linux = cfg!(target_os = "linux");
|
||||||
|
|
||||||
const HEIGHT: Pixels = px(34.);
|
div().flex_shrink_0().child(
|
||||||
|
self.base
|
||||||
div()
|
.flex()
|
||||||
.flex_shrink_0()
|
.flex_row()
|
||||||
.child(
|
.items_center()
|
||||||
self.base
|
.justify_between()
|
||||||
.flex()
|
.h(HEIGHT)
|
||||||
.flex_row()
|
.border_b_1()
|
||||||
.items_center()
|
.border_color(cx.theme().title_bar_border.opacity(0.7))
|
||||||
.justify_between()
|
.bg(cx.theme().title_bar)
|
||||||
.h(HEIGHT)
|
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
||||||
.bg(cx.theme().title_bar)
|
.on_double_click(|_, cx| cx.zoom_window())
|
||||||
.border_b_1()
|
.child(
|
||||||
.border_color(cx.theme().title_bar_border.opacity(0.7))
|
h_flex()
|
||||||
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
|
||||||
.on_double_click(|_, cx| cx.zoom_window())
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.h_full()
|
|
||||||
.justify_between()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.flex_1()
|
|
||||||
.children(self.children),
|
|
||||||
)
|
|
||||||
.child(WindowControls {
|
|
||||||
on_close_window: self.on_close_window,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.when(is_linux, |this| {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.absolute()
|
|
||||||
.size_full()
|
|
||||||
.h_full()
|
.h_full()
|
||||||
.child(TitleBarElement {}),
|
.justify_between()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.flex_1()
|
||||||
|
.when(is_linux, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.top_0()
|
||||||
|
.left_0()
|
||||||
|
.absolute()
|
||||||
|
.size_full()
|
||||||
|
.h_full()
|
||||||
|
.child(TitleBarElement {}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.children(self.children),
|
||||||
)
|
)
|
||||||
})
|
.child(WindowControls {
|
||||||
|
on_close_window: self.on_close_window,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
201
crates/ui/src/window_border.rs
Normal file
201
crates/ui/src/window_border.rs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
// From:
|
||||||
|
// https://github.com/zed-industries/zed/blob/a8afc63a91f6b75528540dcffe73dc8ce0c92ad8/crates/gpui/examples/window_shadow.rs
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, Bounds, CursorStyle,
|
||||||
|
Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement,
|
||||||
|
Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, WindowContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::theme::ActiveTheme;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
pub(crate) const SHADOW_SIZE: Pixels = Pixels(0.0);
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) const SHADOW_SIZE: Pixels = Pixels(12.0);
|
||||||
|
|
||||||
|
pub(crate) const BORDER_SIZE: Pixels = Pixels(1.0);
|
||||||
|
pub(crate) const BORDER_RADIUS: Pixels = Pixels(0.0);
|
||||||
|
|
||||||
|
/// Create a new window border.
|
||||||
|
pub fn window_border() -> WindowBorder {
|
||||||
|
WindowBorder::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Window border use to render a custom window border and shadow for Linux.
|
||||||
|
#[derive(IntoElement, Default)]
|
||||||
|
pub struct WindowBorder {
|
||||||
|
children: Vec<AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the window paddings.
|
||||||
|
pub fn window_paddings(cx: &WindowContext) -> Edges<Pixels> {
|
||||||
|
match cx.window_decorations() {
|
||||||
|
Decorations::Server => Edges::all(px(0.0)),
|
||||||
|
Decorations::Client { tiling } => {
|
||||||
|
let mut paddings = Edges::all(SHADOW_SIZE);
|
||||||
|
if tiling.top {
|
||||||
|
paddings.top = px(0.0);
|
||||||
|
}
|
||||||
|
if tiling.bottom {
|
||||||
|
paddings.bottom = px(0.0);
|
||||||
|
}
|
||||||
|
if tiling.left {
|
||||||
|
paddings.left = px(0.0);
|
||||||
|
}
|
||||||
|
if tiling.right {
|
||||||
|
paddings.right = px(0.0);
|
||||||
|
}
|
||||||
|
paddings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowBorder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParentElement for WindowBorder {
|
||||||
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||||
|
self.children.extend(elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for WindowBorder {
|
||||||
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let decorations = cx.window_decorations();
|
||||||
|
cx.set_client_inset(SHADOW_SIZE);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id("window-backdrop")
|
||||||
|
.bg(gpui::transparent_black())
|
||||||
|
.map(|div| match decorations {
|
||||||
|
Decorations::Server => div,
|
||||||
|
Decorations::Client { tiling, .. } => div
|
||||||
|
.bg(gpui::transparent_black())
|
||||||
|
.child(
|
||||||
|
canvas(
|
||||||
|
|_bounds, cx| {
|
||||||
|
cx.insert_hitbox(
|
||||||
|
Bounds::new(
|
||||||
|
point(px(0.0), px(0.0)),
|
||||||
|
cx.window_bounds().get_bounds().size,
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
move |_bounds, hitbox, cx| {
|
||||||
|
let mouse = cx.mouse_position();
|
||||||
|
let size = cx.window_bounds().get_bounds().size;
|
||||||
|
let Some(edge) = resize_edge(mouse, SHADOW_SIZE, size) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
cx.set_cursor_style(
|
||||||
|
match edge {
|
||||||
|
ResizeEdge::Top | ResizeEdge::Bottom => {
|
||||||
|
CursorStyle::ResizeUpDown
|
||||||
|
}
|
||||||
|
ResizeEdge::Left | ResizeEdge::Right => {
|
||||||
|
CursorStyle::ResizeLeftRight
|
||||||
|
}
|
||||||
|
ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
|
||||||
|
CursorStyle::ResizeUpLeftDownRight
|
||||||
|
}
|
||||||
|
ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
|
||||||
|
CursorStyle::ResizeUpRightDownLeft
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&hitbox,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.size_full()
|
||||||
|
.absolute(),
|
||||||
|
)
|
||||||
|
.when(!(tiling.top || tiling.right), |div| {
|
||||||
|
div.rounded_tr(BORDER_RADIUS)
|
||||||
|
})
|
||||||
|
.when(!(tiling.top || tiling.left), |div| {
|
||||||
|
div.rounded_tl(BORDER_RADIUS)
|
||||||
|
})
|
||||||
|
.when(!tiling.top, |div| div.pt(SHADOW_SIZE))
|
||||||
|
.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();
|
||||||
|
|
||||||
|
if let Some(edge) = resize_edge(pos, SHADOW_SIZE, size) {
|
||||||
|
cx.start_window_resize(edge)
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.size_full()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.map(|div| match decorations {
|
||||||
|
Decorations::Server => div,
|
||||||
|
Decorations::Client { tiling } => div
|
||||||
|
.border_color(cx.theme().window_border)
|
||||||
|
.when(!(tiling.top || tiling.right), |div| {
|
||||||
|
div.rounded_tr(BORDER_RADIUS)
|
||||||
|
})
|
||||||
|
.when(!(tiling.top || tiling.left), |div| {
|
||||||
|
div.rounded_tl(BORDER_RADIUS)
|
||||||
|
})
|
||||||
|
.when(!tiling.top, |div| div.border_t(BORDER_SIZE))
|
||||||
|
.when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
|
||||||
|
.when(!tiling.left, |div| div.border_l(BORDER_SIZE))
|
||||||
|
.when(!tiling.right, |div| div.border_r(BORDER_SIZE))
|
||||||
|
.when(!tiling.is_tiled(), |div| {
|
||||||
|
div.shadow(smallvec::smallvec![gpui::BoxShadow {
|
||||||
|
color: Hsla {
|
||||||
|
h: 0.,
|
||||||
|
s: 0.,
|
||||||
|
l: 0.,
|
||||||
|
a: 0.3,
|
||||||
|
},
|
||||||
|
blur_radius: SHADOW_SIZE / 2.,
|
||||||
|
spread_radius: px(0.),
|
||||||
|
offset: point(px(0.0), px(0.0)),
|
||||||
|
}])
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.on_mouse_move(|_e, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.bg(gpui::transparent_black())
|
||||||
|
.size_full()
|
||||||
|
.children(self.children),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
|
||||||
|
let edge = if pos.y < shadow_size && pos.x < shadow_size {
|
||||||
|
ResizeEdge::TopLeft
|
||||||
|
} else if pos.y < shadow_size && pos.x > size.width - shadow_size {
|
||||||
|
ResizeEdge::TopRight
|
||||||
|
} else if pos.y < shadow_size {
|
||||||
|
ResizeEdge::Top
|
||||||
|
} else if pos.y > size.height - shadow_size && pos.x < shadow_size {
|
||||||
|
ResizeEdge::BottomLeft
|
||||||
|
} else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {
|
||||||
|
ResizeEdge::BottomRight
|
||||||
|
} else if pos.y > size.height - shadow_size {
|
||||||
|
ResizeEdge::Bottom
|
||||||
|
} else if pos.x < shadow_size {
|
||||||
|
ResizeEdge::Left
|
||||||
|
} else if pos.x > size.width - shadow_size {
|
||||||
|
ResizeEdge::Right
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(edge)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user