Add Popover (#6)
This commit is contained in:
parent
a3c14e1515
commit
3288270b49
31 changed files with 569 additions and 91 deletions
35
Cargo.toml
35
Cargo.toml
|
|
@ -20,3 +20,38 @@ anyhow = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = "1.0.203"
|
serde = "1.0.203"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
dbg_macro = "deny"
|
||||||
|
todo = "deny"
|
||||||
|
|
||||||
|
# Motivation: We use `vec![a..b]` a lot when dealing with ranges in text, so
|
||||||
|
# warning on this rule produces a lot of noise.
|
||||||
|
single_range_in_vec_init = "allow"
|
||||||
|
|
||||||
|
# These are all of the rules that currently have violations in the Zed
|
||||||
|
# codebase.
|
||||||
|
#
|
||||||
|
# We'll want to drive this list down by either:
|
||||||
|
# 1. fixing violations of the rule and begin enforcing it
|
||||||
|
# 2. deciding we want to allow the rule permanently, at which point
|
||||||
|
# we should codify that separately above.
|
||||||
|
#
|
||||||
|
# This list shouldn't be added to; it should only get shorter.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# There are a bunch of rules currently failing in the `style` group, so
|
||||||
|
# allow all of those, for now.
|
||||||
|
style = "allow"
|
||||||
|
|
||||||
|
# Individual rules that have violations in the codebase:
|
||||||
|
almost_complete_range = "allow"
|
||||||
|
arc_with_non_send_sync = "allow"
|
||||||
|
borrowed_box = "allow"
|
||||||
|
let_underscore_future = "allow"
|
||||||
|
map_entry = "allow"
|
||||||
|
non_canonical_partial_ord_impl = "allow"
|
||||||
|
reversed_empty_ranges = "allow"
|
||||||
|
type_complexity = "allow"
|
||||||
|
module_inception = "allow"
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ There have a part of UI components from [Zed](https://github.com/zed-industries/
|
||||||
|
|
||||||
- title_bar
|
- title_bar
|
||||||
- picker
|
- picker
|
||||||
|
- popover (based on popup_menu)
|
||||||
|
|
||||||
> I think we can discuss them with Zed team to change the license to MIT or Apache License for share to community.
|
> I think we can discuss them with Zed team to change the license to MIT or Apache License for share to community.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,6 @@ anyhow.workspace = true
|
||||||
rust-embed = "8"
|
rust-embed = "8"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,6 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use gpui::{
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonSize, ButtonStyle},
|
button::{Button, ButtonSize, ButtonStyle},
|
||||||
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName,
|
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::story_case;
|
use super::story_case;
|
||||||
|
|
@ -116,6 +116,26 @@ impl Render for ButtonStory {
|
||||||
.size(ButtonSize::Small)
|
.size(ButtonSize::Small)
|
||||||
.on_click(Self::on_click),
|
.on_click(Self::on_click),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_6()
|
||||||
|
.child(
|
||||||
|
Button::new("button-6", "Selected Button")
|
||||||
|
.style(ButtonStyle::Primary)
|
||||||
|
.selected(true),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("button-7", "Selected Button")
|
||||||
|
.style(ButtonStyle::Secondary)
|
||||||
|
.selected(true),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("button-8", "Selected Button")
|
||||||
|
.style(ButtonStyle::Danger)
|
||||||
|
.selected(true),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use gpui::{
|
use gpui::{IntoElement, ParentElement, Render, Styled, ViewContext, WindowContext};
|
||||||
div, ClickEvent, IntoElement, ParentElement, Render, RenderOnce, Styled, ViewContext,
|
|
||||||
WindowContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ui::{checkbox::Checkbox, h_flex, v_flex, Disableable as _, Selection};
|
use ui::{checkbox::Checkbox, h_flex, v_flex, Disableable as _, Selection};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ mod checkbox_story;
|
||||||
mod dropdown_story;
|
mod dropdown_story;
|
||||||
mod input_story;
|
mod input_story;
|
||||||
mod picker_story;
|
mod picker_story;
|
||||||
|
mod popover_story;
|
||||||
mod switch_story;
|
mod switch_story;
|
||||||
mod tooltip_stroy;
|
mod tooltip_stroy;
|
||||||
|
|
||||||
|
|
@ -25,6 +26,7 @@ use checkbox_story::CheckboxStory;
|
||||||
use dropdown_story::DropdownStory;
|
use dropdown_story::DropdownStory;
|
||||||
use input_story::InputStory;
|
use input_story::InputStory;
|
||||||
use picker_story::PickerStory;
|
use picker_story::PickerStory;
|
||||||
|
use popover_story::PopoverStory;
|
||||||
use switch_story::SwitchStory;
|
use switch_story::SwitchStory;
|
||||||
use tooltip_stroy::TooltipStory;
|
use tooltip_stroy::TooltipStory;
|
||||||
|
|
||||||
|
|
@ -58,6 +60,7 @@ impl StoryContainer {
|
||||||
impl RenderOnce for StoryContainer {
|
impl RenderOnce for StoryContainer {
|
||||||
fn render(self, _: &mut WindowContext) -> impl IntoElement {
|
fn render(self, _: &mut WindowContext) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
|
|
@ -83,6 +86,7 @@ enum StoryType {
|
||||||
Picker,
|
Picker,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Popover,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for StoryType {
|
impl Display for StoryType {
|
||||||
|
|
@ -95,6 +99,7 @@ impl Display for StoryType {
|
||||||
Self::Picker => write!(f, "Picker"),
|
Self::Picker => write!(f, "Picker"),
|
||||||
Self::Dropdown => write!(f, "Dropdown"),
|
Self::Dropdown => write!(f, "Dropdown"),
|
||||||
Self::Tooltip => write!(f, "Tooltip"),
|
Self::Tooltip => write!(f, "Tooltip"),
|
||||||
|
Self::Popover => write!(f, "Popover"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +114,7 @@ pub struct Stories {
|
||||||
picker_story: View<PickerStory>,
|
picker_story: View<PickerStory>,
|
||||||
dropdown_story: View<DropdownStory>,
|
dropdown_story: View<DropdownStory>,
|
||||||
tooltip_story: View<TooltipStory>,
|
tooltip_story: View<TooltipStory>,
|
||||||
|
popover_story: View<PopoverStory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stories {
|
impl Stories {
|
||||||
|
|
@ -119,9 +125,10 @@ impl Stories {
|
||||||
checkbox_story: cx.new_view(|cx| CheckboxStory::new(cx)),
|
checkbox_story: cx.new_view(|cx| CheckboxStory::new(cx)),
|
||||||
input_story: cx.new_view(|cx| InputStory::new(cx)),
|
input_story: cx.new_view(|cx| InputStory::new(cx)),
|
||||||
switch_story: cx.new_view(|cx| SwitchStory::new(cx)),
|
switch_story: cx.new_view(|cx| SwitchStory::new(cx)),
|
||||||
picker_story: cx.new_view(|cx| PickerStory::new(cx)),
|
|
||||||
dropdown_story: cx.new_view(|cx| DropdownStory::new(cx)),
|
|
||||||
tooltip_story: cx.new_view(|_| TooltipStory),
|
tooltip_story: cx.new_view(|_| TooltipStory),
|
||||||
|
picker_story: cx.new_view(PickerStory::new),
|
||||||
|
dropdown_story: cx.new_view(DropdownStory::new),
|
||||||
|
popover_story: cx.new_view(PopoverStory::new),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,6 +160,7 @@ impl Stories {
|
||||||
self.tab("story-picker", StoryType::Picker, None, cx),
|
self.tab("story-picker", StoryType::Picker, None, cx),
|
||||||
self.tab("story-dropdown", StoryType::Dropdown, None, cx),
|
self.tab("story-dropdown", StoryType::Dropdown, None, cx),
|
||||||
self.tab("story-tooltip", StoryType::Tooltip, None, cx),
|
self.tab("story-tooltip", StoryType::Tooltip, None, cx),
|
||||||
|
self.tab("story-popover", StoryType::Popover, None, cx),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,6 +204,7 @@ impl Render for Stories {
|
||||||
StoryType::Picker => this.child(self.picker_story.clone()),
|
StoryType::Picker => this.child(self.picker_story.clone()),
|
||||||
StoryType::Dropdown => this.child(self.dropdown_story.clone()),
|
StoryType::Dropdown => this.child(self.dropdown_story.clone()),
|
||||||
StoryType::Tooltip => this.child(self.tooltip_story.clone()),
|
StoryType::Tooltip => this.child(self.tooltip_story.clone()),
|
||||||
|
StoryType::Popover => this.child(self.popover_story.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder as _, px, Div, Empty, Entity, InteractiveElement as _,
|
actions, div, prelude::FluentBuilder as _, px, InteractiveElement as _, IntoElement,
|
||||||
IntoElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
|
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext as _, WeakView,
|
||||||
VisualContext as _, WeakView, WindowContext,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
|
|
||||||
72
crates/ui-story/src/popover_story.rs
Normal file
72
crates/ui-story/src/popover_story.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use gpui::{div, px, Element, IntoElement, ParentElement as _, Render, Styled as _, ViewContext};
|
||||||
|
use ui::{
|
||||||
|
button::{Button, ButtonSize},
|
||||||
|
divider::Divider,
|
||||||
|
h_flex,
|
||||||
|
popover::{Popover, PopoverContent},
|
||||||
|
v_flex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::story_case;
|
||||||
|
|
||||||
|
pub struct PopoverStory {}
|
||||||
|
|
||||||
|
impl PopoverStory {
|
||||||
|
pub fn new(_: &mut ViewContext<Self>) -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for PopoverStory {
|
||||||
|
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
story_case(
|
||||||
|
"Popover",
|
||||||
|
"Displays rich content in a portal, triggered by a button.",
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex().items_center().justify_between().child(
|
||||||
|
Popover::new("info-top-left")
|
||||||
|
.trigger(Button::new("info", "Top Left").width(px(300.)))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(|_| {
|
||||||
|
v_flex().gap_4().child("Hello, this is a Popover.")
|
||||||
|
.child(Divider::horizontal())
|
||||||
|
.child(
|
||||||
|
Button::new("info1", "Yes")
|
||||||
|
.width(px(80.))
|
||||||
|
.size(ButtonSize::Small)
|
||||||
|
).into_any()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
).child(
|
||||||
|
Popover::new("info-top-right")
|
||||||
|
.trigger(Button::new("info", "Top Right").width(px(300.)))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(|_|
|
||||||
|
"Hello, this is a Popover.\nYou can click outside to dissmis.".into_any(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().absolute().bottom_4().left_0().w_full().h_10().child(
|
||||||
|
h_flex().items_center().justify_between().child(
|
||||||
|
Popover::new("info-bottom-left")
|
||||||
|
.trigger(Button::new("pop", "Bottom Left").width(px(300.)))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(|_| "这是另外一个 Popover。\n你可以点击外部来关闭。\nThis popover has position bottom_4().left_0().w_full().h_10().".into_any(), cx)
|
||||||
|
})).child(
|
||||||
|
Popover::new("info-bottom-right")
|
||||||
|
.trigger(Button::new("pop", "Bottom Right").width(px(300.)))
|
||||||
|
.content(|cx| {
|
||||||
|
PopoverContent::new(|_| "这是另外一个 Popover。\n你可以点击外部来关闭。\nThis popover has position bottom_4().left_0().w_full().h_10().".into_any(), cx)
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ pub struct SwitchStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwitchStory {
|
impl SwitchStory {
|
||||||
pub(crate) fn new(cx: &mut WindowContext) -> Self {
|
pub(crate) fn new(_cx: &mut WindowContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
switch1: false,
|
switch1: false,
|
||||||
switch2: true,
|
switch2: true,
|
||||||
|
|
@ -98,14 +98,14 @@ impl Render for SwitchStory {
|
||||||
card(cx).v_flex() .items_start().child(title("Disabled Switchs")).child(
|
card(cx).v_flex() .items_start().child(title("Disabled Switchs")).child(
|
||||||
h_flex().items_center()
|
h_flex().items_center()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.child(Switch::new("switch3").disabled(true).on_click(|ev, cx| {
|
.child(Switch::new("switch3").disabled(true).on_click(|ev, _| {
|
||||||
println!("Switch value changed: {:?}", ev);
|
println!("Switch value changed: {:?}", ev);
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
Switch::new("switch3_1").label("Airplane Mode")
|
Switch::new("switch3_1").label("Airplane Mode")
|
||||||
.checked(true)
|
.checked(true)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.on_click(|ev, cx| {
|
.on_click(|ev, _| {
|
||||||
println!("Switch value changed: {:?}", ev);
|
println!("Switch value changed: {:?}", ev);
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,6 @@ wry = "0"
|
||||||
smallvec = "1.13.2"
|
smallvec = "1.13.2"
|
||||||
windows = "0.57.0"
|
windows = "0.57.0"
|
||||||
unicode-segmentation = "1.11.0"
|
unicode-segmentation = "1.11.0"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use gpui::{
|
|
||||||
div, prelude::FluentBuilder as _, px, AnyElement, ClickEvent, DefiniteLength, Div, ElementId,
|
|
||||||
Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
|
|
||||||
StatefulInteractiveElement as _, Styled, WindowContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
h_flex, span,
|
h_flex,
|
||||||
theme::{ActiveTheme, Colorize as _, ThemeMode},
|
theme::{ActiveTheme, Colorize as _, ThemeMode},
|
||||||
Clickable, Disableable, Icon, IconName, Selectable,
|
Clickable, Disableable, Icon, Selectable,
|
||||||
|
};
|
||||||
|
use gpui::{
|
||||||
|
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, Hsla,
|
||||||
|
InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
|
||||||
|
StatefulInteractiveElement as _, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum ButtonRounded {
|
pub enum ButtonRounded {
|
||||||
|
|
@ -134,6 +133,8 @@ impl RenderOnce for Button {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
|
.when_some(self.width, |this, width| this.w(width))
|
||||||
|
.when_some(self.height, |this, height| this.h(height))
|
||||||
.map(|this| match self.size {
|
.map(|this| match self.size {
|
||||||
ButtonSize::Small => this.px_3().py_2().h_6(),
|
ButtonSize::Small => this.px_3().py_2().h_6(),
|
||||||
ButtonSize::Medium => this.px_4().py_2().h_8(),
|
ButtonSize::Medium => this.px_4().py_2().h_8(),
|
||||||
|
|
@ -144,7 +145,12 @@ impl RenderOnce for Button {
|
||||||
ButtonRounded::Large => this.rounded(px(cx.theme().radius * 2.0)),
|
ButtonRounded::Large => this.rounded(px(cx.theme().radius * 2.0)),
|
||||||
ButtonRounded::None => this.rounded_none(),
|
ButtonRounded::None => this.rounded_none(),
|
||||||
})
|
})
|
||||||
.when(!self.disabled, |this| {
|
.when(self.selected, |this| {
|
||||||
|
let selected_style = style.selected(cx);
|
||||||
|
this.bg(selected_style.bg)
|
||||||
|
.border_color(selected_style.border)
|
||||||
|
})
|
||||||
|
.when(!self.disabled && !self.selected, |this| {
|
||||||
this.hover(|this| {
|
this.hover(|this| {
|
||||||
let hover_style = style.hovered(cx);
|
let hover_style = style.hovered(cx);
|
||||||
this.bg(hover_style.bg).border_color(hover_style.border)
|
this.bg(hover_style.bg).border_color(hover_style.border)
|
||||||
|
|
@ -174,7 +180,13 @@ impl RenderOnce for Button {
|
||||||
})
|
})
|
||||||
.border_1()
|
.border_1()
|
||||||
.map(|this| match theme.mode {
|
.map(|this| match theme.mode {
|
||||||
ThemeMode::Light => this.shadow_sm(),
|
ThemeMode::Light => {
|
||||||
|
if self.disabled {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
this.shadow_sm()
|
||||||
|
}
|
||||||
|
}
|
||||||
ThemeMode::Dark => this,
|
ThemeMode::Dark => this,
|
||||||
})
|
})
|
||||||
.child({
|
.child({
|
||||||
|
|
@ -251,15 +263,23 @@ impl ButtonStyle {
|
||||||
|
|
||||||
fn active(&self, cx: &WindowContext) -> ButtonStyles {
|
fn active(&self, cx: &WindowContext) -> ButtonStyles {
|
||||||
let bg = self.bg_color(cx).darken(0.05);
|
let bg = self.bg_color(cx).darken(0.05);
|
||||||
let border = self.border_color(cx).darken(0.05);
|
let border = self.border_color(cx);
|
||||||
let fg = self.text_color(cx).darken(0.05);
|
let fg = self.text_color(cx).darken(0.05);
|
||||||
|
|
||||||
ButtonStyles { bg, border, fg }
|
ButtonStyles { bg, border, fg }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selected(&self, cx: &WindowContext) -> ButtonStyles {
|
||||||
|
let bg = self.bg_color(cx).darken(0.07);
|
||||||
|
let border = self.border_color(cx);
|
||||||
|
let fg = self.text_color(cx).darken(0.07);
|
||||||
|
|
||||||
|
ButtonStyles { bg, border, fg }
|
||||||
|
}
|
||||||
|
|
||||||
fn disabled(&self, cx: &WindowContext) -> ButtonStyles {
|
fn disabled(&self, cx: &WindowContext) -> ButtonStyles {
|
||||||
let bg = self.bg_color(cx).grayscale();
|
let bg = self.bg_color(cx).grayscale().opacity(0.9);
|
||||||
let border = self.border_color(cx).grayscale();
|
let border = self.border_color(cx).grayscale().opacity(0.9);
|
||||||
let fg = self.text_color(cx).grayscale();
|
let fg = self.text_color(cx).grayscale();
|
||||||
|
|
||||||
ButtonStyles { bg, border, fg }
|
ButtonStyles { bg, border, fg }
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::FluentBuilder as _, px, svg, ElementId, InteractiveElement, IntoElement,
|
prelude::FluentBuilder as _, svg, ElementId, InteractiveElement, IntoElement, ParentElement,
|
||||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _,
|
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _, WindowContext,
|
||||||
WindowContext,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
disableable::Disableable,
|
disableable::Disableable,
|
||||||
label::Label,
|
|
||||||
selectable::{Selectable, Selection},
|
selectable::{Selectable, Selection},
|
||||||
stock::{h_flex, v_flex},
|
stock::{h_flex, v_flex},
|
||||||
theme::{ActiveTheme, Colorize as _},
|
theme::{ActiveTheme, Colorize as _},
|
||||||
|
|
@ -25,7 +23,7 @@ pub struct Checkbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Checkbox {
|
impl Checkbox {
|
||||||
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
|
pub fn new(id: impl Into<ElementId>, _: &mut WindowContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
checked: Selection::Unselected,
|
checked: Selection::Unselected,
|
||||||
|
|
@ -95,7 +93,7 @@ impl RenderOnce for Checkbox {
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(color)
|
.border_color(color)
|
||||||
.rounded_sm()
|
.rounded_sm()
|
||||||
.size(px(16.))
|
.size_4()
|
||||||
.map(|this| match self.checked {
|
.map(|this| match self.checked {
|
||||||
Selection::Unselected => this.bg(theme.transparent),
|
Selection::Unselected => this.bg(theme.transparent),
|
||||||
_ => this.bg(color),
|
_ => this.bg(color),
|
||||||
|
|
@ -123,7 +121,7 @@ impl RenderOnce for Checkbox {
|
||||||
)
|
)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if let Some(label) = self.label {
|
if let Some(label) = self.label {
|
||||||
this.child(Label::new(label).text_color(color))
|
this.child(label).text_color(color)
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui::IntoElement;
|
use gpui::IntoElement;
|
||||||
use gpui::{div, prelude::FluentBuilder as _, Div, RenderOnce, Styled as _};
|
use gpui::{div, prelude::FluentBuilder as _, RenderOnce, Styled as _};
|
||||||
|
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
use crate::StyledExt as _;
|
use crate::StyledExt as _;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder as _, px, AppContext, Div, ElementId, FocusHandle,
|
actions, div, prelude::FluentBuilder as _, px, AppContext, ElementId, FocusHandle,
|
||||||
FocusableView, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render,
|
FocusableView, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render,
|
||||||
RenderOnce, SharedString, Stateful, StatefulInteractiveElement as _, Styled as _, View,
|
SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext,
|
||||||
ViewContext, VisualContext as _, WeakView,
|
VisualContext as _, WeakView,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(dropdown, [Up, Down, Enter, Escape]);
|
actions!(dropdown, [Up, Down, Enter, Escape]);
|
||||||
|
|
@ -24,7 +22,7 @@ use crate::{
|
||||||
list::ListItem,
|
list::ListItem,
|
||||||
picker::{self, Picker, PickerDelegate},
|
picker::{self, Picker, PickerDelegate},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
v_flex, Icon, IconName,
|
Icon, IconName,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait for items that can be displayed in a dropdown.
|
/// A trait for items that can be displayed in a dropdown.
|
||||||
|
|
@ -96,7 +94,7 @@ where
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
if let Some(view) = self.dropdown.upgrade() {
|
if let Some(view) = self.dropdown.upgrade() {
|
||||||
cx.update_view(&view, |view, cx| {
|
cx.update_view(&view, |view, _| {
|
||||||
view.open = false;
|
view.open = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use gpui::{ClickEvent, Element, Focusable, InteractiveElement, Stateful, WindowContext};
|
use gpui::{ClickEvent, Focusable, InteractiveElement, WindowContext};
|
||||||
|
|
||||||
pub trait InterativeElementExt: InteractiveElement {
|
pub trait InterativeElementExt: InteractiveElement {
|
||||||
/// Set the listener for a double click event.
|
/// Set the listener for a double click event.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, rgb, svg, AnyElement, Div, Hsla, InteractiveElement, IntoElement, ParentElement as _,
|
svg, AnyElement, Hsla, IntoElement, RenderOnce, SharedString, StyleRefinement, Styled, Svg,
|
||||||
RenderOnce, SharedString, StyleRefinement, Styled, Svg, TextStyle, TextStyleRefinement,
|
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -33,9 +32,9 @@ impl IconName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Icon> for IconName {
|
impl From<IconName> for Icon {
|
||||||
fn into(self) -> Icon {
|
fn from(val: IconName) -> Self {
|
||||||
Icon::new(self)
|
Icon::new(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,8 +88,8 @@ impl RenderOnce for Icon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<AnyElement> for Icon {
|
impl From<Icon> for AnyElement {
|
||||||
fn into(self) -> AnyElement {
|
fn from(val: Icon) -> Self {
|
||||||
self.into_any_element()
|
val.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use gpui::{
|
use gpui::{div, Div, IntoElement, ParentElement, RenderOnce, SharedString, Styled, WindowContext};
|
||||||
div, prelude::FluentBuilder as _, px, AbsoluteLength, DefiniteLength, Div, Hsla, IntoElement,
|
|
||||||
ParentElement, RenderOnce, SharedString, Styled, TextStyleRefinement, WindowContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ pub mod dropdown;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod picker;
|
pub mod picker;
|
||||||
|
pub mod popover;
|
||||||
pub mod switch;
|
pub mod switch;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
pub mod tooltip;
|
pub mod tooltip;
|
||||||
|
|
@ -37,4 +38,5 @@ pub fn init(cx: &mut gpui::AppContext) {
|
||||||
input::init(cx);
|
input::init(cx);
|
||||||
picker::init(cx);
|
picker::init(cx);
|
||||||
dropdown::init(cx);
|
dropdown::init(cx);
|
||||||
|
popover::init(cx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement,
|
prelude::FluentBuilder as _, ClickEvent, Div, ElementId, InteractiveElement, IntoElement,
|
||||||
IntoElement, MouseButton, MouseDownEvent, ParentElement, RenderOnce, SharedString, Stateful,
|
MouseButton, MouseDownEvent, ParentElement, RenderOnce, Stateful,
|
||||||
StatefulInteractiveElement as _, Style, Styled, WindowContext,
|
StatefulInteractiveElement as _, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable};
|
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable};
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,8 @@ pub trait PickerDelegate: Sized + 'static {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>) {}
|
fn confirm(&mut self, _secondary: bool, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {}
|
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||||
fn should_dismiss(&self) -> bool {
|
fn should_dismiss(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ pub trait PickerDelegate: Sized + 'static {
|
||||||
fn separators_after_indices(&self) -> Vec<usize> {
|
fn separators_after_indices(&self) -> Vec<usize> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
fn update_matches(&mut self, query: &str, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
fn update_matches(&mut self, _query: &str, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
Task::ready(())
|
Task::ready(())
|
||||||
}
|
}
|
||||||
fn confirm_update_query(&mut self, _cx: &mut ViewContext<Picker<Self>>) -> Option<String> {
|
fn confirm_update_query(&mut self, _cx: &mut ViewContext<Picker<Self>>) -> Option<String> {
|
||||||
|
|
@ -472,7 +472,6 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
self.set_query(text, cx);
|
self.set_query(text, cx);
|
||||||
self.refresh(cx);
|
self.refresh(cx);
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
319
crates/ui/src/popover.rs
Normal file
319
crates/ui/src/popover.rs
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use crate::theme::ActiveTheme;
|
||||||
|
use crate::{Clickable, Selectable, StyledExt};
|
||||||
|
use gpui::{
|
||||||
|
actions, anchored, deferred, div, prelude::FluentBuilder as _, AnchorCorner, AnyElement,
|
||||||
|
AppContext, Bounds, DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView,
|
||||||
|
HitboxId, InteractiveElement, IntoElement, LayoutId, ManagedView, ParentElement, Pixels,
|
||||||
|
Render, Style, Styled, View, ViewContext, VisualContext, WindowContext,
|
||||||
|
};
|
||||||
|
use gpui::{point, px, DispatchPhase, MouseDownEvent, Point};
|
||||||
|
|
||||||
|
actions!(popover, [Dismiss]);
|
||||||
|
|
||||||
|
pub trait Triggerable: IntoElement + Clickable + Selectable + 'static {}
|
||||||
|
impl<T: IntoElement + Clickable + Selectable + 'static> Triggerable for T {}
|
||||||
|
|
||||||
|
pub fn init(_cx: &mut AppContext) {}
|
||||||
|
|
||||||
|
pub struct PopoverContent {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
content: Rc<dyn Fn(&mut WindowContext) -> AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopoverContent {
|
||||||
|
pub fn new<B>(content: B, cx: &mut WindowContext) -> View<Self>
|
||||||
|
where
|
||||||
|
B: Fn(&mut WindowContext) -> AnyElement + 'static,
|
||||||
|
{
|
||||||
|
cx.new_view(|cx| {
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
focus_handle,
|
||||||
|
content: Rc::new(content),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl EventEmitter<DismissEvent> for PopoverContent {}
|
||||||
|
|
||||||
|
impl FocusableView for PopoverContent {
|
||||||
|
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for PopoverContent {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.id("test")
|
||||||
|
// .on_action(cx.listener(Self::dissmiss))
|
||||||
|
.absolute()
|
||||||
|
.mt_2()
|
||||||
|
.elevation_2(cx)
|
||||||
|
.bg(cx.theme().popover)
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.p_4()
|
||||||
|
.max_w_128()
|
||||||
|
.w_80()
|
||||||
|
.occlude()
|
||||||
|
.child(self.content.clone()(cx))
|
||||||
|
.on_mouse_down_out(cx.listener(|_, _, cx| cx.emit(DismissEvent)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Popover<M: ManagedView> {
|
||||||
|
id: ElementId,
|
||||||
|
anchor: AnchorCorner,
|
||||||
|
trigger_builder: Option<
|
||||||
|
Box<
|
||||||
|
dyn FnOnce(
|
||||||
|
Rc<RefCell<Option<View<M>>>>,
|
||||||
|
Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
||||||
|
) -> AnyElement
|
||||||
|
+ 'static,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
content_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: ManagedView> Popover<M> {
|
||||||
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
trigger_builder: None,
|
||||||
|
content_builder: None,
|
||||||
|
anchor: AnchorCorner::TopLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger<T: Triggerable>(mut self, trigger: T) -> Self {
|
||||||
|
self.trigger_builder = Some(Box::new(|popover, builder| {
|
||||||
|
let open = popover.borrow_mut().is_some();
|
||||||
|
trigger
|
||||||
|
.selected(open)
|
||||||
|
.when_some(builder, |this, builder| {
|
||||||
|
this.on_click(move |_, cx| Self::show_popover(&builder, &popover, cx))
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
|
}));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content(
|
||||||
|
mut self,
|
||||||
|
content_builder: impl Fn(&mut WindowContext) -> View<M> + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.content_builder = Some(Rc::new(content_builder));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// anchor defines which corner of the menu to anchor to the attachment point
|
||||||
|
/// (by default the cursor position, but see attach)
|
||||||
|
pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
|
||||||
|
self.anchor = anchor;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_popover(
|
||||||
|
builder: &Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>,
|
||||||
|
popover: &Rc<RefCell<Option<View<M>>>>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) {
|
||||||
|
let new_popover = (builder)(cx);
|
||||||
|
|
||||||
|
let popover_cloned = popover.clone();
|
||||||
|
let prev_focus_handle = cx.focused();
|
||||||
|
|
||||||
|
cx.subscribe(&new_popover, move |modal, _: &DismissEvent, cx| {
|
||||||
|
if modal.focus_handle(cx).contains_focused(cx) {
|
||||||
|
if let Some(prev_focus_handle) = prev_focus_handle.as_ref() {
|
||||||
|
cx.focus(prev_focus_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*popover_cloned.borrow_mut() = None;
|
||||||
|
cx.refresh();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
cx.focus_view(&new_popover);
|
||||||
|
*popover.borrow_mut() = Some(new_popover);
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolved_attach(&self) -> AnchorCorner {
|
||||||
|
match self.anchor {
|
||||||
|
AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
|
||||||
|
AnchorCorner::TopRight => AnchorCorner::BottomRight,
|
||||||
|
AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
|
||||||
|
AnchorCorner::BottomRight => AnchorCorner::TopRight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolved_offset(&self, _cx: &WindowContext) -> Point<Pixels> {
|
||||||
|
let offset = px(0.);
|
||||||
|
match self.anchor {
|
||||||
|
AnchorCorner::TopRight | AnchorCorner::BottomRight => point(offset, px(0.)),
|
||||||
|
AnchorCorner::TopLeft | AnchorCorner::BottomLeft => point(-offset, px(0.)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PopoverElementState<M> {
|
||||||
|
trigger_bounds: Option<Bounds<Pixels>>,
|
||||||
|
popover: Rc<RefCell<Option<View<M>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Clone for PopoverElementState<M> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
popover: Rc::clone(&self.popover),
|
||||||
|
trigger_bounds: self.trigger_bounds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for PopoverElementState<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
popover: Rc::default(),
|
||||||
|
trigger_bounds: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PopoverFrameState {
|
||||||
|
trigger_layout_id: Option<LayoutId>,
|
||||||
|
trigger_element: Option<AnyElement>,
|
||||||
|
popover_element: Option<AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: ManagedView> IntoElement for Popover<M> {
|
||||||
|
type Element = Self;
|
||||||
|
|
||||||
|
fn into_element(self) -> Self::Element {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: ManagedView> Element for Popover<M> {
|
||||||
|
type RequestLayoutState = PopoverFrameState;
|
||||||
|
type PrepaintState = Option<HitboxId>;
|
||||||
|
|
||||||
|
fn id(&self) -> Option<ElementId> {
|
||||||
|
Some(self.id.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_layout(
|
||||||
|
&mut self,
|
||||||
|
global_id: Option<&gpui::GlobalElementId>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
|
cx.with_element_state(
|
||||||
|
global_id.unwrap(),
|
||||||
|
|element_state: Option<PopoverElementState<M>>, cx| {
|
||||||
|
let element_state = element_state.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut popover_layout_id = None;
|
||||||
|
let popover_element = element_state.popover.borrow_mut().as_mut().map(|popover| {
|
||||||
|
let mut anchored = anchored().snap_to_window().anchor(self.anchor);
|
||||||
|
if let Some(trigger_bounds) = element_state.trigger_bounds {
|
||||||
|
anchored = anchored.position(
|
||||||
|
self.resolved_attach().corner(trigger_bounds)
|
||||||
|
+ self.resolved_offset(cx),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut element = deferred(anchored.child(popover.clone()))
|
||||||
|
.with_priority(1)
|
||||||
|
.into_any();
|
||||||
|
|
||||||
|
popover_layout_id = Some(element.request_layout(cx));
|
||||||
|
element
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut trigger_element = self.trigger_builder.take().map(|builder| {
|
||||||
|
builder(element_state.popover.clone(), self.content_builder.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
let trigger_layout_id = trigger_element
|
||||||
|
.as_mut()
|
||||||
|
.map(|trigger_element| trigger_element.request_layout(cx));
|
||||||
|
|
||||||
|
let layout_id = cx.request_layout(
|
||||||
|
Style::default(),
|
||||||
|
popover_layout_id.into_iter().chain(trigger_layout_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
(
|
||||||
|
layout_id,
|
||||||
|
PopoverFrameState {
|
||||||
|
trigger_layout_id,
|
||||||
|
trigger_element,
|
||||||
|
popover_element,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
element_state,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepaint(
|
||||||
|
&mut self,
|
||||||
|
global_id: Option<&gpui::GlobalElementId>,
|
||||||
|
_: gpui::Bounds<gpui::Pixels>,
|
||||||
|
request_layout: &mut Self::RequestLayoutState,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Self::PrepaintState {
|
||||||
|
if let Some(element) = &mut request_layout.trigger_element {
|
||||||
|
element.prepaint(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(element) = &mut request_layout.popover_element {
|
||||||
|
element.prepaint(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
request_layout.trigger_layout_id.map(|layout_id| {
|
||||||
|
let bounds = cx.layout_bounds(layout_id);
|
||||||
|
cx.with_element_state(global_id.unwrap(), |element_state, _cx| {
|
||||||
|
let mut element_state: PopoverElementState<M> = element_state.unwrap();
|
||||||
|
element_state.trigger_bounds = Some(bounds);
|
||||||
|
((), element_state)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.insert_hitbox(bounds, false).id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Option<&gpui::GlobalElementId>,
|
||||||
|
_: gpui::Bounds<gpui::Pixels>,
|
||||||
|
request_layout: &mut Self::RequestLayoutState,
|
||||||
|
trigger_hitbox: &mut Option<HitboxId>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) {
|
||||||
|
if let Some(element) = &mut request_layout.trigger_element {
|
||||||
|
element.paint(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(element) = &mut request_layout.popover_element {
|
||||||
|
element.paint(cx);
|
||||||
|
|
||||||
|
if let Some(hitbox) = *trigger_hitbox {
|
||||||
|
// Mouse-downing outside the menu dismisses it, so we don't
|
||||||
|
// want a click on the toggle to re-open it.
|
||||||
|
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||||
|
cx.stop_propagation()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! The prelude of this crate. When building UI in Zed you almost always want to import this.
|
//! The prelude of this crate. When building UI in Zed you almost always want to import this.
|
||||||
|
|
||||||
pub use gpui::prelude::*;
|
pub use gpui::prelude::*;
|
||||||
|
#[allow(unused_imports)]
|
||||||
pub use gpui::{
|
pub use gpui::{
|
||||||
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
|
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
|
||||||
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext,
|
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::theme::{ActiveTheme, Colorize};
|
use crate::theme::ActiveTheme;
|
||||||
use gpui::{hsla, point, px, BoxShadow, Styled, WindowContext};
|
use gpui::{hsla, point, px, BoxShadow, Styled, WindowContext};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
pub enum ElevationIndex {
|
pub enum ElevationIndex {
|
||||||
Surface,
|
Surface,
|
||||||
ElevatedSurface,
|
PopoverSurface,
|
||||||
ModalSurface,
|
ModalSurface,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ impl ElevationIndex {
|
||||||
match self {
|
match self {
|
||||||
ElevationIndex::Surface => smallvec![],
|
ElevationIndex::Surface => smallvec![],
|
||||||
|
|
||||||
ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
|
ElevationIndex::PopoverSurface => smallvec![BoxShadow {
|
||||||
color: hsla(0., 0., 0., 0.12),
|
color: hsla(0., 0., 0., 0.12),
|
||||||
offset: point(px(0.), px(2.)),
|
offset: point(px(0.), px(2.)),
|
||||||
blur_radius: px(3.),
|
blur_radius: px(3.),
|
||||||
|
|
@ -22,26 +22,18 @@ impl ElevationIndex {
|
||||||
|
|
||||||
ElevationIndex::ModalSurface => smallvec![
|
ElevationIndex::ModalSurface => smallvec![
|
||||||
BoxShadow {
|
BoxShadow {
|
||||||
color: hsla(0., 0., 0., 0.12),
|
color: hsla(0., 0., 0., 0.1),
|
||||||
offset: point(px(0.), px(2.)),
|
offset: point(px(0.), px(4.)),
|
||||||
blur_radius: px(3.),
|
|
||||||
spread_radius: px(0.),
|
|
||||||
},
|
|
||||||
BoxShadow {
|
|
||||||
color: hsla(0., 0., 0., 0.08),
|
|
||||||
offset: point(px(0.), px(3.)),
|
|
||||||
blur_radius: px(6.),
|
blur_radius: px(6.),
|
||||||
spread_radius: px(0.),
|
spread_radius: px(-1.),
|
||||||
},
|
},
|
||||||
BoxShadow {
|
BoxShadow {
|
||||||
color: hsla(0., 0., 0., 0.04),
|
color: hsla(0., 0., 0., 0.1),
|
||||||
offset: point(px(0.), px(6.)),
|
offset: point(px(0.), px(2.)),
|
||||||
blur_radius: px(12.),
|
blur_radius: px(4.),
|
||||||
spread_radius: px(0.),
|
spread_radius: px(-2.),
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
_ => smallvec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +71,7 @@ pub trait StyledExt: Styled + Sized {
|
||||||
|
|
||||||
/// Appear above most UI elements
|
/// Appear above most UI elements
|
||||||
fn elevation_2(self, cx: &mut WindowContext) -> Self {
|
fn elevation_2(self, cx: &mut WindowContext) -> Self {
|
||||||
elevated(self, cx, ElevationIndex::ElevatedSurface)
|
elevated(self, cx, ElevationIndex::PopoverSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Above all other UI elements and are located above the wash layer
|
// Above all other UI elements and are located above the wash layer
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::selectable::Selectable;
|
use crate::selectable::Selectable;
|
||||||
use crate::theme::{ActiveTheme, Colorize};
|
use crate::theme::{ActiveTheme, Colorize};
|
||||||
use crate::Icon;
|
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AnyElement, Div, IntoElement, ParentElement as _, RenderOnce, SharedString, Stateful,
|
div, px, AnyElement, Div, IntoElement, ParentElement as _, RenderOnce, SharedString, Stateful,
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ impl Colors {
|
||||||
Colors {
|
Colors {
|
||||||
title_bar_background: hsl(0., 0., 12.),
|
title_bar_background: hsl(0., 0., 12.),
|
||||||
background: hsl(0.0, 0.0, 6.0),
|
background: hsl(0.0, 0.0, 6.0),
|
||||||
foreground: hsl(240., 5., 65.),
|
foreground: hsl(0., 0., 98.),
|
||||||
card: hsl(299.0, 2., 9.),
|
card: hsl(299.0, 2., 9.),
|
||||||
card_foreground: hsl(0.0, 0.0, 98.0),
|
card_foreground: hsl(0.0, 0.0, 98.0),
|
||||||
popover: hsl(240.0, 10.0, 3.9),
|
popover: hsl(240.0, 10.0, 3.9),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use gpui::{
|
||||||
SharedString, Styled, ViewContext, VisualContext, WindowContext,
|
SharedString, Styled, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{h_flex, styled_ext::ElevationIndex, theme::ActiveTheme, v_flex};
|
use crate::{h_flex, theme::ActiveTheme, v_flex, StyledExt};
|
||||||
|
|
||||||
pub struct Tooltip {
|
pub struct Tooltip {
|
||||||
title: SharedString,
|
title: SharedString,
|
||||||
|
|
@ -63,7 +63,7 @@ pub fn tooltip_container<V>(
|
||||||
.rounded(px(8.))
|
.rounded(px(8.))
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.shadow(ElevationIndex::ElevatedSurface.shadow())
|
.elevation_2(cx)
|
||||||
.text_color(cx.theme().popover_foreground)
|
.text_color(cx.theme().popover_foreground)
|
||||||
.py_1p5()
|
.py_1p5()
|
||||||
.px_2()
|
.px_2()
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,6 @@ ui.workspace = true
|
||||||
ui-story.workspace = true
|
ui-story.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::Weak;
|
||||||
|
|
||||||
use gpui::{AppContext, Global};
|
use gpui::{AppContext, Global};
|
||||||
|
|
||||||
pub struct AppState {}
|
pub struct AppState {}
|
||||||
|
|
||||||
struct GlobalAppState(Weak<AppState>);
|
struct GlobalAppState();
|
||||||
|
|
||||||
impl Global for GlobalAppState {}
|
impl Global for GlobalAppState {}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn set_global(app_state: Weak<AppState>, cx: &mut AppContext) {
|
pub fn set_global(_app_state: Weak<AppState>, cx: &mut AppContext) {
|
||||||
cx.set_global(GlobalAppState(app_state));
|
cx.set_global(GlobalAppState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,6 @@ impl Render for Workspace {
|
||||||
.label_side(LabelSide::Left)
|
.label_side(LabelSide::Left)
|
||||||
.label("Dark Mode")
|
.label("Dark Mode")
|
||||||
.on_click(move |_, cx| {
|
.on_click(move |_, cx| {
|
||||||
dbg!("theme-mode clicked");
|
|
||||||
let mode = match cx.theme().mode.is_dark() {
|
let mode = match cx.theme().mode.is_dark() {
|
||||||
false => ui::theme::ThemeMode::Dark,
|
false => ui::theme::ThemeMode::Dark,
|
||||||
true => ui::theme::ThemeMode::Light,
|
true => ui::theme::ThemeMode::Light,
|
||||||
|
|
@ -185,6 +184,13 @@ impl Render for Workspace {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.children(self.render_notifications(cx))
|
.children(self.render_notifications(cx))
|
||||||
.child(div().flex().px_4().gap_2().child(self.stories.clone()))
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_1()
|
||||||
|
.px_4()
|
||||||
|
.gap_2()
|
||||||
|
.child(self.stories.clone()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue