Add slide down animation t Modal. (#141)
- Export cx.active_modal and cx.active_drawer - Add support custom modal style, and add show_close option.
This commit is contained in:
parent
1da82c5453
commit
3550b1ee13
5 changed files with 204 additions and 114 deletions
|
|
@ -11,10 +11,12 @@ use workspace::{TitleBar, Workspace};
|
|||
use std::sync::Arc;
|
||||
use ui::{
|
||||
button::{Button, ButtonStyle},
|
||||
drawer::Drawer,
|
||||
modal::Modal,
|
||||
popover::Popover,
|
||||
popup_menu::PopupMenu,
|
||||
theme::{ActiveTheme, Theme},
|
||||
IconName, Root, Sizable,
|
||||
ContextModal as _, IconName, Root, Sizable,
|
||||
};
|
||||
|
||||
use crate::app_state::AppState;
|
||||
|
|
@ -271,6 +273,10 @@ pub fn open_new(
|
|||
|
||||
impl Render for StoryWorkspace {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_modal = cx.active_modal();
|
||||
let active_drawer = cx.active_drawer();
|
||||
let has_active_modal = active_modal.is_some();
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
|
|
@ -331,6 +337,16 @@ impl Render for StoryWorkspace {
|
|||
),
|
||||
)
|
||||
.child(self.workspace.clone())
|
||||
.when(!has_active_modal, |this| {
|
||||
this.when_some(active_drawer, |this, builder| {
|
||||
let drawer = Drawer::new(cx);
|
||||
this.child(builder(drawer, cx))
|
||||
})
|
||||
})
|
||||
.when_some(active_modal, |this, builder| {
|
||||
let modal = Modal::new(cx);
|
||||
this.child(builder(modal, cx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl StoryContainer {
|
|||
}
|
||||
|
||||
impl Render for StoryContainer {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("story-container")
|
||||
.size_full()
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ pub struct ModalStory {
|
|||
input1: View<TextInput>,
|
||||
date_picker: View<DatePicker>,
|
||||
modal_overlay: bool,
|
||||
model_show_close: bool,
|
||||
model_padding: bool,
|
||||
}
|
||||
|
||||
impl ModalStory {
|
||||
|
|
@ -235,6 +237,8 @@ impl ModalStory {
|
|||
input1,
|
||||
date_picker,
|
||||
modal_overlay: true,
|
||||
model_show_close: true,
|
||||
model_padding: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,6 +295,64 @@ impl ModalStory {
|
|||
self.drawer_placement = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn show_modal(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let overlay = self.modal_overlay;
|
||||
let modal_show_close = self.model_show_close;
|
||||
let modal_padding = self.model_padding;
|
||||
let input1 = self.input1.clone();
|
||||
let date_picker = self.date_picker.clone();
|
||||
let view = cx.view().clone();
|
||||
|
||||
cx.open_modal(move |modal, cx| {
|
||||
modal
|
||||
.title("Form Modal")
|
||||
.overlay(overlay)
|
||||
.show_close(modal_show_close)
|
||||
.when(!modal_padding, |this| this.p(px(0.)))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.child("This is a modal dialog.")
|
||||
.child("You can put anything here.")
|
||||
.child(input1.clone())
|
||||
.child(date_picker.clone()),
|
||||
)
|
||||
.footer(
|
||||
h_flex()
|
||||
.gap_6()
|
||||
.items_center()
|
||||
.child(
|
||||
Button::new("confirm", cx)
|
||||
.primary()
|
||||
.label("Confirm")
|
||||
.on_click({
|
||||
let view = view.clone();
|
||||
let input1 = input1.clone();
|
||||
let date_picker = date_picker.clone();
|
||||
|
||||
move |_, cx| {
|
||||
cx.close_modal();
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.selected_value = Some(
|
||||
format!(
|
||||
"Hello, {}, date: {}",
|
||||
input1.read(cx).text(),
|
||||
date_picker.read(cx).date()
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(Button::new("cancel", cx).label("Cancel").on_click(|_, cx| {
|
||||
cx.close_modal();
|
||||
})),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ModalStory {
|
||||
|
|
@ -301,17 +363,40 @@ impl FocusableView for ModalStory {
|
|||
|
||||
impl Render for ModalStory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div().child(
|
||||
div().size_full().child(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.child(
|
||||
Checkbox::new("modal-overlay")
|
||||
.label("Modal Overlay")
|
||||
.checked(self.modal_overlay)
|
||||
.on_click(cx.listener(|view, _, cx| {
|
||||
view.modal_overlay = !view.modal_overlay;
|
||||
cx.notify();
|
||||
})),
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
Checkbox::new("modal-overlay")
|
||||
.label("Modal Overlay")
|
||||
.checked(self.modal_overlay)
|
||||
.on_click(cx.listener(|view, _, cx| {
|
||||
view.modal_overlay = !view.modal_overlay;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Checkbox::new("modal-show-close")
|
||||
.label("Model Close Button")
|
||||
.checked(self.model_show_close)
|
||||
.on_click(cx.listener(|view, _, cx| {
|
||||
view.model_show_close = !view.model_show_close;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Checkbox::new("modal-padding")
|
||||
.label("Model Padding")
|
||||
.checked(self.model_padding)
|
||||
.on_click(cx.listener(|view, _, cx| {
|
||||
view.model_padding = !view.model_padding;
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
|
@ -358,63 +443,7 @@ impl Render for ModalStory {
|
|||
.child(
|
||||
Button::new("show-modal", cx)
|
||||
.label("Open Modal...")
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
let overlay = this.modal_overlay;
|
||||
let input1 = this.input1.clone();
|
||||
let date_picker = this.date_picker.clone();
|
||||
let view = cx.view().clone();
|
||||
|
||||
cx.open_modal(move |modal, cx| {
|
||||
modal
|
||||
.title("Form Modal")
|
||||
.overlay(overlay)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.child("This is a modal dialog.")
|
||||
.child("You can put anything here.")
|
||||
.child(input1.clone())
|
||||
.child(date_picker.clone()),
|
||||
)
|
||||
.footer(
|
||||
h_flex()
|
||||
.gap_6()
|
||||
.items_center()
|
||||
.child(
|
||||
Button::new("confirm", cx)
|
||||
.primary()
|
||||
.label("Confirm")
|
||||
.on_click({
|
||||
let view = view.clone();
|
||||
let input1 = input1.clone();
|
||||
let date_picker = date_picker.clone();
|
||||
|
||||
move |_, cx| {
|
||||
cx.close_modal();
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.selected_value = Some(
|
||||
format!(
|
||||
"Hello, {}, date: {}",
|
||||
input1.read(cx).text(),
|
||||
date_picker.read(cx).date()
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("cancel", cx).label("Cancel").on_click(
|
||||
|_, cx| {
|
||||
cx.close_modal();
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
})),
|
||||
.on_click(cx.listener(|this, _, cx| this.show_modal(cx))),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::rc::Rc;
|
||||
use std::{rc::Rc, time::Duration};
|
||||
|
||||
use gpui::{
|
||||
anchored, div, hsla, prelude::FluentBuilder, px, AnyElement, Bounds, ClickEvent, Div,
|
||||
FocusHandle, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
|
||||
Point, RenderOnce, Styled, WindowContext,
|
||||
anchored, div, hsla, prelude::FluentBuilder, px, Animation, AnimationExt as _, AnyElement,
|
||||
Bounds, ClickEvent, Div, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement,
|
||||
Pixels, Point, RenderOnce, Styled, WindowContext,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Modal {
|
||||
focus_handle: FocusHandle,
|
||||
base: Div,
|
||||
title: Option<AnyElement>,
|
||||
footer: Option<AnyElement>,
|
||||
content: Div,
|
||||
|
|
@ -20,6 +20,7 @@ pub struct Modal {
|
|||
max_width: Option<Pixels>,
|
||||
margin_top: Option<Pixels>,
|
||||
on_close: Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
||||
show_close: bool,
|
||||
overlay: bool,
|
||||
}
|
||||
|
||||
|
|
@ -37,8 +38,18 @@ pub(crate) fn overlay_color(overlay: bool, cx: &WindowContext) -> Hsla {
|
|||
|
||||
impl Modal {
|
||||
pub fn new(cx: &mut WindowContext) -> Self {
|
||||
let base = v_flex()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded_lg()
|
||||
.shadow_xl()
|
||||
.min_h_48()
|
||||
.p_4()
|
||||
.gap_4();
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
base,
|
||||
title: None,
|
||||
footer: None,
|
||||
content: v_flex(),
|
||||
|
|
@ -47,6 +58,7 @@ impl Modal {
|
|||
max_width: None,
|
||||
overlay: true,
|
||||
on_close: Rc::new(|_, _| {}),
|
||||
show_close: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +83,12 @@ impl Modal {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the false to hide close icon, default: true
|
||||
pub fn show_close(mut self, show_close: bool) -> Self {
|
||||
self.show_close = show_close;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the top offset of the modal, defaults to None, will use the 1/10 of the viewport height.
|
||||
pub fn margin_top(mut self, margin_top: Pixels) -> Self {
|
||||
self.margin_top = Some(margin_top);
|
||||
|
|
@ -102,6 +120,12 @@ impl ParentElement for Modal {
|
|||
}
|
||||
}
|
||||
|
||||
impl Styled for Modal {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Modal {
|
||||
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement {
|
||||
let on_close = self.on_close.clone();
|
||||
|
|
@ -129,40 +153,41 @@ impl RenderOnce for Modal {
|
|||
})
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
self.base
|
||||
.id("modal")
|
||||
.absolute()
|
||||
.track_focus(&self.focus_handle)
|
||||
.occlude()
|
||||
.relative()
|
||||
.left(x)
|
||||
.top(y)
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded_lg()
|
||||
.shadow_xl()
|
||||
.min_h_48()
|
||||
.w(self.width)
|
||||
.when_some(self.max_width, |this, w| this.max_w(w))
|
||||
.p_4()
|
||||
.gap_4()
|
||||
.children(self.title)
|
||||
.child(
|
||||
Button::new("close", cx)
|
||||
.absolute()
|
||||
.top_2()
|
||||
.right_2()
|
||||
.small()
|
||||
.ghost()
|
||||
.icon(IconName::Close)
|
||||
.on_click(move |_, cx| {
|
||||
on_close(&ClickEvent::default(), cx);
|
||||
cx.close_modal();
|
||||
}),
|
||||
)
|
||||
.when(self.show_close, |this| {
|
||||
this.child(
|
||||
Button::new("close", cx)
|
||||
.absolute()
|
||||
.top_2()
|
||||
.right_2()
|
||||
.small()
|
||||
.ghost()
|
||||
.icon(IconName::Close)
|
||||
.on_click(move |_, cx| {
|
||||
on_close(&ClickEvent::default(), cx);
|
||||
cx.close_modal();
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(self.content)
|
||||
.children(self.footer),
|
||||
.children(self.footer)
|
||||
.with_animation(
|
||||
"slide-down",
|
||||
Animation::new(Duration::from_secs_f64(0.1)),
|
||||
move |this, delta| {
|
||||
let y_offset = px(-30.) + delta * px(30.);
|
||||
this.top(y + y_offset)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, AnyView, ParentElement as _, Render, Styled, ViewContext,
|
||||
WindowContext,
|
||||
use gpui::{div, AnyView, ParentElement as _, Render, Styled, ViewContext, WindowContext};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{ops::DerefMut, rc::Rc};
|
||||
|
||||
use crate::{drawer::Drawer, modal::Modal, theme::ActiveTheme};
|
||||
|
||||
|
|
@ -13,6 +13,9 @@ pub trait ContextModal: Sized {
|
|||
where
|
||||
F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static;
|
||||
|
||||
/// Return the active Drawer builder, you must add the Drawer to the view.
|
||||
fn active_drawer(&self) -> Option<Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>>;
|
||||
|
||||
/// Closes the active Drawer.
|
||||
fn close_drawer(&mut self);
|
||||
|
||||
|
|
@ -21,6 +24,9 @@ pub trait ContextModal: Sized {
|
|||
where
|
||||
F: Fn(Modal, &mut WindowContext) -> Modal + 'static;
|
||||
|
||||
/// Return the active Modal builder, you must add the Modal to the view.
|
||||
fn active_modal(&self) -> Option<Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>>;
|
||||
|
||||
/// Closes the active Modal.
|
||||
fn close_modal(&mut self);
|
||||
}
|
||||
|
|
@ -36,6 +42,10 @@ impl<'a> ContextModal for WindowContext<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn active_drawer(&self) -> Option<Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>> {
|
||||
Root::read_root(&self).active_drawer.clone()
|
||||
}
|
||||
|
||||
fn close_drawer(&mut self) {
|
||||
Root::update_root(self, |root, cx| {
|
||||
root.active_drawer = None;
|
||||
|
|
@ -53,6 +63,10 @@ impl<'a> ContextModal for WindowContext<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn active_modal(&self) -> Option<Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>> {
|
||||
Root::read_root(&self).active_modal.clone()
|
||||
}
|
||||
|
||||
fn close_modal(&mut self) {
|
||||
Root::update_root(self, |root, cx| {
|
||||
root.active_modal = None;
|
||||
|
|
@ -68,6 +82,10 @@ impl<'a, V> ContextModal for ViewContext<'a, V> {
|
|||
self.deref_mut().open_drawer(build)
|
||||
}
|
||||
|
||||
fn active_modal(&self) -> Option<Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>> {
|
||||
self.deref().active_modal()
|
||||
}
|
||||
|
||||
fn close_drawer(&mut self) {
|
||||
self.deref_mut().close_drawer()
|
||||
}
|
||||
|
|
@ -79,6 +97,10 @@ impl<'a, V> ContextModal for ViewContext<'a, V> {
|
|||
self.deref_mut().open_modal(build)
|
||||
}
|
||||
|
||||
fn active_drawer(&self) -> Option<Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>> {
|
||||
self.deref().active_drawer()
|
||||
}
|
||||
|
||||
fn close_modal(&mut self) {
|
||||
self.deref_mut().close_modal()
|
||||
}
|
||||
|
|
@ -91,7 +113,7 @@ pub struct Root {
|
|||
}
|
||||
|
||||
impl Root {
|
||||
pub fn new(root_view: AnyView, _cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(root_view: AnyView, _: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
active_drawer: None,
|
||||
active_modal: None,
|
||||
|
|
@ -111,25 +133,23 @@ impl Root {
|
|||
|
||||
root.update(cx, |root, cx| f(root, cx))
|
||||
}
|
||||
|
||||
fn read_root<'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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Root {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||
let has_modal = self.active_modal.is_some();
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(self.root_view.clone())
|
||||
.when(!has_modal, |this| {
|
||||
this.when_some(self.active_drawer.clone(), |this, build| {
|
||||
let drawer = Drawer::new(cx);
|
||||
this.child(build(drawer, cx))
|
||||
})
|
||||
})
|
||||
.when_some(self.active_modal.clone(), |this, build| {
|
||||
let modal = Modal::new(cx);
|
||||
this.child(build(modal, cx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue