Merge branch 'main' into add-app-events

This commit is contained in:
Joseph T. Lyons
2023-11-22 16:16:11 -05:00
171 changed files with 8567 additions and 10047 deletions
+1 -5
View File
@@ -19,16 +19,12 @@ runs:
- name: Limit target directory size
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 70
run: script/clear-target-dir-if-larger-than 100
- name: Run check
env:
RUSTFLAGS: -D warnings
shell: bash -euxo pipefail {0}
run: cargo check --tests --workspace
- name: Run tests
env:
RUSTFLAGS: -D warnings
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast
+4 -1
View File
@@ -23,6 +23,9 @@ jobs:
- self-hosted
- test
steps:
- name: Set up default .cargo/config.toml
run: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml
- name: Checkout repo
uses: actions/checkout@v3
with:
@@ -87,7 +90,7 @@ jobs:
submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70
run: script/clear-target-dir-if-larger-than 100
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+1 -1
View File
@@ -79,7 +79,7 @@ jobs:
submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70
run: script/clear-target-dir-if-larger-than 100
- name: Set release channel to nightly
run: |
Generated
+42 -13
View File
@@ -1664,7 +1664,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.28.0"
version = "0.29.0"
dependencies = [
"anyhow",
"async-trait",
@@ -8031,6 +8031,35 @@ dependencies = [
"workspace",
]
[[package]]
name = "search2"
version = "0.1.0"
dependencies = [
"anyhow",
"bitflags 1.3.2",
"client2",
"collections",
"editor2",
"futures 0.3.28",
"gpui2",
"language2",
"log",
"menu2",
"postage",
"project2",
"serde",
"serde_derive",
"serde_json",
"settings2",
"smallvec",
"smol",
"theme2",
"ui2",
"unindent",
"util",
"workspace2",
]
[[package]]
name = "security-framework"
version = "2.9.2"
@@ -8854,6 +8883,13 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "story"
version = "0.1.0"
dependencies = [
"gpui2",
]
[[package]]
name = "storybook2"
version = "0.1.0"
@@ -8875,6 +8911,7 @@ dependencies = [
"settings2",
"simplelog",
"smallvec",
"story",
"strum",
"theme",
"theme2",
@@ -8882,17 +8919,6 @@ dependencies = [
"util",
]
[[package]]
name = "storybook3"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui2",
"settings2",
"theme2",
"ui2",
]
[[package]]
name = "stringprep"
version = "0.1.4"
@@ -9396,6 +9422,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings2",
"story",
"toml 0.5.11",
"util",
"uuid 1.4.1",
@@ -10239,6 +10266,7 @@ dependencies = [
"serde",
"settings2",
"smallvec",
"story",
"strum",
"theme2",
]
@@ -11465,7 +11493,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.114.0"
version = "0.115.0"
dependencies = [
"activity_indicator",
"ai",
@@ -11660,6 +11688,7 @@ dependencies = [
"rsa 0.4.0",
"rust-embed",
"schemars",
"search2",
"serde",
"serde_derive",
"serde_json",
+2 -1
View File
@@ -90,6 +90,7 @@ members = [
"crates/rpc",
"crates/rpc2",
"crates/search",
"crates/search2",
"crates/settings",
"crates/settings2",
"crates/snippet",
@@ -97,7 +98,6 @@ members = [
"crates/sqlez_macros",
"crates/rich_text",
"crates/storybook2",
"crates/storybook3",
"crates/sum_tree",
"crates/terminal",
"crates/terminal2",
@@ -110,6 +110,7 @@ members = [
"crates/ui2",
"crates/util",
"crates/semantic_index",
"crates/story",
"crates/vim",
"crates/vcs_menu",
"crates/workspace2",
+1 -1
View File
@@ -43,7 +43,7 @@
"calt": false
},
// The default font size for text in the UI
"ui_font_size": 14,
"ui_font_size": 16,
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
+2 -1
View File
@@ -7,5 +7,6 @@
// custom settings, run the `open default settings` command
// from the command palette or from `Zed` application menu.
{
"buffer_font_size": 15
"ui_font_size": 16,
"buffer_font_size": 16
}
@@ -8,8 +8,8 @@ pub struct UpdateNotification {
impl EventEmitter<NotificationEvent> for UpdateNotification {}
impl Render<Self> for UpdateNotification {
type Element = Div<Self>;
impl Render for UpdateNotification {
type Element = Div;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div().child("Updated zed!")
+1 -1
View File
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.28.0"
version = "0.29.0"
publish = false
[[bin]]
+2 -2
View File
@@ -3294,8 +3294,8 @@ impl CollabPanel {
// .with_width(size.x())
// }
impl Render<Self> for CollabPanel {
type Element = Focusable<Self, Div<Self>>;
impl Render for CollabPanel {
type Element = Focusable<Div>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div()
@@ -37,7 +37,7 @@ use gpui::{
};
use project::Project;
use theme::ActiveTheme;
use ui::{h_stack, Button, ButtonVariant, KeyBinding, Label, TextColor, Tooltip};
use ui::{h_stack, Button, ButtonVariant, Color, KeyBinding, Label, Tooltip};
use workspace::Workspace;
// const MAX_PROJECT_NAME_LENGTH: usize = 40;
@@ -81,8 +81,8 @@ pub struct CollabTitlebarItem {
_subscriptions: Vec<Subscription>,
}
impl Render<Self> for CollabTitlebarItem {
type Element = Stateful<Self, Div<Self>>;
impl Render for CollabTitlebarItem {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
h_stack()
@@ -100,7 +100,7 @@ impl Render<Self> for CollabTitlebarItem {
|s| s.pl(px(68.)),
)
.bg(cx.theme().colors().title_bar_background)
.on_click(|_, event, cx| {
.on_click(|event, cx| {
if event.up.click_count == 2 {
cx.zoom_window();
}
@@ -115,16 +115,16 @@ impl Render<Self> for CollabTitlebarItem {
.child(
Button::new("player")
.variant(ButtonVariant::Ghost)
.color(Some(TextColor::Player(0))),
.color(Some(Color::Player(0))),
)
.tooltip(move |_, cx| Tooltip::text("Toggle following", cx)),
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
)
// TODO - Add project menu
.child(
div()
.id("titlebar_project_menu_button")
.child(Button::new("project_name").variant(ButtonVariant::Ghost))
.tooltip(move |_, cx| Tooltip::text("Recent Projects", cx)),
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
)
// TODO - Add git menu
.child(
@@ -133,9 +133,9 @@ impl Render<Self> for CollabTitlebarItem {
.child(
Button::new("branch_name")
.variant(ButtonVariant::Ghost)
.color(Some(TextColor::Muted)),
.color(Some(Color::Muted)),
)
.tooltip(move |_, cx| {
.tooltip(move |cx| {
cx.build_view(|_| {
Tooltip::new("Recent Branches")
.key_binding(KeyBinding::new(gpui::KeyBinding::new(
@@ -76,8 +76,8 @@ impl FocusableView for CommandPalette {
}
}
impl Render<Self> for CommandPalette {
type Element = Div<Self>;
impl Render for CommandPalette {
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w_96().child(self.picker.clone())
@@ -140,7 +140,7 @@ impl CommandPaletteDelegate {
}
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = Div<Picker<Self>>;
type ListItem = Div;
fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into()
+12 -13
View File
@@ -16,7 +16,7 @@ use gpui::{
actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent,
FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, Model,
ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext,
VisualContext, WeakView,
VisualContext, WeakView, WindowContext,
};
use language::{
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -36,7 +36,7 @@ use std::{
};
use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
use ui::{h_stack, HighlightedLabel, Icon, IconElement, Label, TextColor};
use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label};
use util::TryFutureExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -90,8 +90,8 @@ struct DiagnosticGroupState {
impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
impl Render<Self> for ProjectDiagnosticsEditor {
type Element = Focusable<Self, Div<Self>>;
impl Render for ProjectDiagnosticsEditor {
type Element = Focusable<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let child = if self.path_states.is_empty() {
@@ -109,8 +109,8 @@ impl Render<Self> for ProjectDiagnosticsEditor {
div()
.track_focus(&self.focus_handle)
.size_full()
.on_focus_in(Self::focus_in)
.on_action(Self::toggle_warnings)
.on_focus_in(cx.listener(Self::focus_in))
.on_action(cx.listener(Self::toggle_warnings))
.child(child)
}
}
@@ -662,7 +662,7 @@ impl Item for ProjectDiagnosticsEditor {
Some("Project Diagnostics".into())
}
fn tab_content<T: 'static>(&self, _detail: Option<usize>, _: &AppContext) -> AnyElement<T> {
fn tab_content(&self, _detail: Option<usize>, _: &WindowContext) -> AnyElement {
render_summary(&self.summary)
}
@@ -742,7 +742,7 @@ impl Item for ProjectDiagnosticsEditor {
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft { flex: None }
ToolbarItemLocation::PrimaryLeft
}
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -778,15 +778,15 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.bg(gpui::red())
.map(|stack| {
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
IconElement::new(Icon::XCircle).color(TextColor::Error)
IconElement::new(Icon::XCircle).color(Color::Error)
} else {
IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning)
IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
};
stack.child(div().pl_8().child(icon))
})
.when_some(diagnostic.source.as_ref(), |stack, source| {
stack.child(Label::new(format!("{source}:")).color(TextColor::Accent))
stack.child(Label::new(format!("{source}:")).color(Color::Accent))
})
.child(HighlightedLabel::new(message.clone(), highlights.clone()))
.when_some(diagnostic.code.as_ref(), |stack, code| {
@@ -796,11 +796,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
})
}
pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> {
pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement {
if summary.error_count == 0 && summary.warning_count == 0 {
let label = Label::new("No problems");
label.render_into_any()
//.render()
} else {
h_stack()
.bg(gpui::red())
+12 -12
View File
@@ -7,7 +7,7 @@ use gpui::{
use language::Diagnostic;
use lsp::LanguageServerId;
use theme::ActiveTheme;
use ui::{h_stack, Icon, IconElement, Label, TextColor, Tooltip};
use ui::{h_stack, Color, Icon, IconElement, Label, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
use crate::ProjectDiagnosticsEditor;
@@ -21,31 +21,31 @@ pub struct DiagnosticIndicator {
_observe_active_editor: Option<Subscription>,
}
impl Render<Self> for DiagnosticIndicator {
type Element = Stateful<Self, Div<Self>>;
impl Render for DiagnosticIndicator {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
(0, 0) => h_stack().child(IconElement::new(Icon::Check).color(TextColor::Success)),
(0, 0) => h_stack().child(IconElement::new(Icon::Check).color(Color::Success)),
(0, warning_count) => h_stack()
.gap_1()
.child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))
.child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
.child(Label::new(warning_count.to_string())),
(error_count, 0) => h_stack()
.gap_1()
.child(IconElement::new(Icon::XCircle).color(TextColor::Error))
.child(IconElement::new(Icon::XCircle).color(Color::Error))
.child(Label::new(error_count.to_string())),
(error_count, warning_count) => h_stack()
.gap_1()
.child(IconElement::new(Icon::XCircle).color(TextColor::Error))
.child(IconElement::new(Icon::XCircle).color(Color::Error))
.child(Label::new(error_count.to_string()))
.child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))
.child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
.child(Label::new(warning_count.to_string())),
};
h_stack()
.id(cx.entity_id())
.on_action(Self::go_to_next_diagnostic)
.on_action(cx.listener(Self::go_to_next_diagnostic))
.rounded_md()
.flex_none()
.h(rems(1.375))
@@ -54,14 +54,14 @@ impl Render<Self> for DiagnosticIndicator {
.bg(cx.theme().colors().ghost_element_background)
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
.tooltip(|_, cx| Tooltip::text("Project Diagnostics", cx))
.on_click(|this, _, cx| {
.tooltip(|cx| Tooltip::text("Project Diagnostics", cx))
.on_click(cx.listener(|this, _, cx| {
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
})
}
})
}))
.child(diagnostic_indicator)
}
}
+6 -6
View File
@@ -7,8 +7,8 @@ pub struct ToolbarControls {
editor: Option<WeakView<ProjectDiagnosticsEditor>>,
}
impl Render<Self> for ToolbarControls {
type Element = Div<Self>;
impl Render for ToolbarControls {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let include_warnings = self
@@ -26,14 +26,14 @@ impl Render<Self> for ToolbarControls {
div().child(
IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
.tooltip(move |_, cx| Tooltip::text(tooltip, cx))
.on_click(|this: &mut Self, cx| {
.tooltip(move |cx| Tooltip::text(tooltip, cx))
.on_click(cx.listener(|this, _, cx| {
if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
editor.update(cx, |editor, cx| {
editor.toggle_warnings(&Default::default(), cx);
});
}
}),
})),
)
}
}
@@ -49,7 +49,7 @@ impl ToolbarItemView for ToolbarControls {
if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
self.editor = Some(editor.downgrade());
ToolbarItemLocation::PrimaryRight { flex: None }
ToolbarItemLocation::PrimaryRight
} else {
ToolbarItemLocation::Hidden
}
+3 -3
View File
@@ -50,7 +50,7 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32);
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>;
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>;
pub struct Block {
id: BlockId,
@@ -69,7 +69,7 @@ where
pub position: P,
pub height: u8,
pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>,
pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
pub disposition: BlockDisposition,
}
@@ -947,7 +947,7 @@ impl DerefMut for BlockContext<'_, '_> {
}
impl Block {
pub fn render(&self, cx: &mut BlockContext) -> AnyElement<Editor> {
pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
self.render.lock()(cx)
}
+283 -277
View File
@@ -44,7 +44,7 @@ use gpui::{
EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -54,13 +54,13 @@ use itertools::Itertools;
pub use language::{char_kind, CharKind};
use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,
Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, LanguageRegistry,
LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
use lazy_static::lazy_static;
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
use lsp::{DiagnosticSeverity, LanguageServerId};
use movement::TextLayoutDetails;
use multi_buffer::ToOffsetUtf16;
pub use multi_buffer::{
@@ -97,7 +97,7 @@ use text::{OffsetUtf16, Rope};
use theme::{
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
};
use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};
use ui::{h_stack, v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{
item::{ItemEvent, ItemHandle},
@@ -907,7 +907,7 @@ impl ContextMenu {
style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, AnyElement<Editor>) {
) -> (DisplayPoint, AnyElement) {
match self {
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
@@ -1223,209 +1223,203 @@ impl CompletionsMenu {
style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement<Editor> {
todo!("old implementation below")
) -> AnyElement {
let settings = EditorSettings::get_global(cx);
let show_completion_documentation = settings.show_completion_documentation;
let widest_completion_ix = self
.matches
.iter()
.enumerate()
.max_by_key(|(_, mat)| {
let completions = self.completions.read();
let completion = &completions[mat.candidate_id];
let documentation = &completion.documentation;
let mut len = completion.label.text.chars().count();
if let Some(Documentation::SingleLine(text)) = documentation {
if show_completion_documentation {
len += text.chars().count();
}
}
len
})
.map(|(ix, _)| ix);
let completions = self.completions.clone();
let matches = self.matches.clone();
let selected_item = self.selected_item;
let list = uniform_list(
cx.view().clone(),
"completions",
matches.len(),
move |editor, range, cx| {
let start_ix = range.start;
let completions_guard = completions.read();
matches[range]
.iter()
.enumerate()
.map(|(ix, mat)| {
let item_ix = start_ix + ix;
let candidate_id = mat.candidate_id;
let completion = &completions_guard[candidate_id];
let documentation = if show_completion_documentation {
&completion.documentation
} else {
&None
};
// todo!("highlights")
// let highlights = combine_syntax_and_fuzzy_match_highlights(
// &completion.label.text,
// style.text.color.into(),
// styled_runs_for_code_label(&completion.label, &style.syntax),
// &mat.positions,
// )
// todo!("documentation")
// MouseEventHandler::new::<CompletionTag, _>(mat.candidate_id, cx, |state, _| {
// let completion_label = HighlightedLabel::new(
// completion.label.text.clone(),
// combine_syntax_and_fuzzy_match_highlights(
// &completion.label.text,
// style.text.color.into(),
// styled_runs_for_code_label(&completion.label, &style.syntax),
// &mat.positions,
// ),
// );
// Text::new(completion.label.text.clone(), style.text.clone())
// .with_soft_wrap(false)
// .with_highlights();
// if let Some(Documentation::SingleLine(text)) = documentation {
// h_stack()
// .child(completion_label)
// .with_children((|| {
// let text_style = TextStyle {
// color: style.autocomplete.inline_docs_color,
// font_size: style.text.font_size
// * style.autocomplete.inline_docs_size_percent,
// ..style.text.clone()
// };
// let label = Text::new(text.clone(), text_style)
// .aligned()
// .constrained()
// .dynamically(move |constraint, _, _| gpui::SizeConstraint {
// min: constraint.min,
// max: vec2f(constraint.max.x(), constraint.min.y()),
// });
// if Some(item_ix) == widest_completion_ix {
// Some(
// label
// .contained()
// .with_style(style.autocomplete.inline_docs_container)
// .into_any(),
// )
// } else {
// Some(label.flex_float().into_any())
// }
// })())
// .into_any()
// } else {
// completion_label.into_any()
// }
// .contained()
// .with_style(item_style)
// .constrained()
// .dynamically(move |constraint, _, _| {
// if Some(item_ix) == widest_completion_ix {
// constraint
// } else {
// gpui::SizeConstraint {
// min: constraint.min,
// max: constraint.min,
// }
// }
// })
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .on_down(MouseButton::Left, move |_, this, cx| {
// this.confirm_completion(
// &ConfirmCompletion {
// item_ix: Some(item_ix),
// },
// cx,
// )
// .map(|task| task.detach());
// })
// .constrained()
//
div()
.id(mat.candidate_id)
.whitespace_nowrap()
.overflow_hidden()
.bg(gpui::green())
.hover(|style| style.bg(gpui::blue()))
.when(item_ix == selected_item, |div| div.bg(gpui::red()))
.child(SharedString::from(completion.label.text.clone()))
.min_w(px(300.))
.max_w(px(700.))
})
.collect()
},
)
.track_scroll(self.scroll_handle.clone())
.with_width_from_item(widest_completion_ix);
list.render_into_any()
// todo!("multiline documentation")
// enum MultiLineDocumentation {}
// Flex::row()
// .with_child(list.flex(1., false))
// .with_children({
// let mat = &self.matches[selected_item];
// let completions = self.completions.read();
// let completion = &completions[mat.candidate_id];
// let documentation = &completion.documentation;
// match documentation {
// Some(Documentation::MultiLinePlainText(text)) => Some(
// Flex::column()
// .scrollable::<MultiLineDocumentation>(0, None, cx)
// .with_child(
// Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
// )
// .contained()
// .with_style(style.autocomplete.alongside_docs_container)
// .constrained()
// .with_max_width(style.autocomplete.alongside_docs_max_width)
// .flex(1., false),
// ),
// Some(Documentation::MultiLineMarkdown(parsed)) => Some(
// Flex::column()
// .scrollable::<MultiLineDocumentation>(0, None, cx)
// .with_child(render_parsed_markdown::<MultiLineDocumentation>(
// parsed, &style, workspace, cx,
// ))
// .contained()
// .with_style(style.autocomplete.alongside_docs_container)
// .constrained()
// .with_max_width(style.autocomplete.alongside_docs_max_width)
// .flex(1., false),
// ),
// _ => None,
// }
// })
// .contained()
// .with_style(style.autocomplete.container)
// .into_any()
}
// enum CompletionTag {}
// let settings = EditorSettings>(cx);
// let show_completion_documentation = settings.show_completion_documentation;
// let widest_completion_ix = self
// .matches
// .iter()
// .enumerate()
// .max_by_key(|(_, mat)| {
// let completions = self.completions.read();
// let completion = &completions[mat.candidate_id];
// let documentation = &completion.documentation;
// let mut len = completion.label.text.chars().count();
// if let Some(Documentation::SingleLine(text)) = documentation {
// if show_completion_documentation {
// len += text.chars().count();
// }
// }
// len
// })
// .map(|(ix, _)| ix);
// let completions = self.completions.clone();
// let matches = self.matches.clone();
// let selected_item = self.selected_item;
// let list = UniformList::new(self.list.clone(), matches.len(), cx, {
// let style = style.clone();
// move |_, range, items, cx| {
// let start_ix = range.start;
// let completions_guard = completions.read();
// for (ix, mat) in matches[range].iter().enumerate() {
// let item_ix = start_ix + ix;
// let candidate_id = mat.candidate_id;
// let completion = &completions_guard[candidate_id];
// let documentation = if show_completion_documentation {
// &completion.documentation
// } else {
// &None
// };
// items.push(
// MouseEventHandler::new::<CompletionTag, _>(
// mat.candidate_id,
// cx,
// |state, _| {
// let item_style = if item_ix == selected_item {
// style.autocomplete.selected_item
// } else if state.hovered() {
// style.autocomplete.hovered_item
// } else {
// style.autocomplete.item
// };
// let completion_label =
// Text::new(completion.label.text.clone(), style.text.clone())
// .with_soft_wrap(false)
// .with_highlights(
// combine_syntax_and_fuzzy_match_highlights(
// &completion.label.text,
// style.text.color.into(),
// styled_runs_for_code_label(
// &completion.label,
// &style.syntax,
// ),
// &mat.positions,
// ),
// );
// if let Some(Documentation::SingleLine(text)) = documentation {
// Flex::row()
// .with_child(completion_label)
// .with_children((|| {
// let text_style = TextStyle {
// color: style.autocomplete.inline_docs_color,
// font_size: style.text.font_size
// * style.autocomplete.inline_docs_size_percent,
// ..style.text.clone()
// };
// let label = Text::new(text.clone(), text_style)
// .aligned()
// .constrained()
// .dynamically(move |constraint, _, _| {
// gpui::SizeConstraint {
// min: constraint.min,
// max: vec2f(
// constraint.max.x(),
// constraint.min.y(),
// ),
// }
// });
// if Some(item_ix) == widest_completion_ix {
// Some(
// label
// .contained()
// .with_style(
// style
// .autocomplete
// .inline_docs_container,
// )
// .into_any(),
// )
// } else {
// Some(label.flex_float().into_any())
// }
// })())
// .into_any()
// } else {
// completion_label.into_any()
// }
// .contained()
// .with_style(item_style)
// .constrained()
// .dynamically(
// move |constraint, _, _| {
// if Some(item_ix) == widest_completion_ix {
// constraint
// } else {
// gpui::SizeConstraint {
// min: constraint.min,
// max: constraint.min,
// }
// }
// },
// )
// },
// )
// .with_cursor_style(CursorStyle::PointingHand)
// .on_down(MouseButton::Left, move |_, this, cx| {
// this.confirm_completion(
// &ConfirmCompletion {
// item_ix: Some(item_ix),
// },
// cx,
// )
// .map(|task| task.detach());
// })
// .constrained()
// .with_min_width(style.autocomplete.completion_min_width)
// .with_max_width(style.autocomplete.completion_max_width)
// .into_any(),
// );
// }
// }
// })
// .with_width_from_item(widest_completion_ix);
// enum MultiLineDocumentation {}
// Flex::row()
// .with_child(list.flex(1., false))
// .with_children({
// let mat = &self.matches[selected_item];
// let completions = self.completions.read();
// let completion = &completions[mat.candidate_id];
// let documentation = &completion.documentation;
// match documentation {
// Some(Documentation::MultiLinePlainText(text)) => Some(
// Flex::column()
// .scrollable::<MultiLineDocumentation>(0, None, cx)
// .with_child(
// Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
// )
// .contained()
// .with_style(style.autocomplete.alongside_docs_container)
// .constrained()
// .with_max_width(style.autocomplete.alongside_docs_max_width)
// .flex(1., false),
// ),
// Some(Documentation::MultiLineMarkdown(parsed)) => Some(
// Flex::column()
// .scrollable::<MultiLineDocumentation>(0, None, cx)
// .with_child(render_parsed_markdown::<MultiLineDocumentation>(
// parsed, &style, workspace, cx,
// ))
// .contained()
// .with_style(style.autocomplete.alongside_docs_container)
// .constrained()
// .with_max_width(style.autocomplete.alongside_docs_max_width)
// .flex(1., false),
// ),
// _ => None,
// }
// })
// .contained()
// .with_style(style.autocomplete.container)
// .into_any()
// }
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
let mut matches = if let Some(query) = query {
fuzzy::match_strings(
@@ -1541,13 +1535,15 @@ impl CodeActionsMenu {
mut cursor_position: DisplayPoint,
style: &EditorStyle,
cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, AnyElement<Editor>) {
) -> (DisplayPoint, AnyElement) {
let actions = self.actions.clone();
let selected_item = self.selected_item;
let element = uniform_list(
cx.view().clone(),
"code_actions_menu",
self.actions.len(),
move |editor, range, cx| {
move |this, range, cx| {
actions[range.clone()]
.iter()
.enumerate()
@@ -1569,17 +1565,20 @@ impl CodeActionsMenu {
.bg(colors.element_hover)
.text_color(colors.text_accent)
})
.on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| {
cx.stop_propagation();
editor
.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
cx,
)
.map(|task| task.detach_and_log_err(cx));
})
.on_mouse_down(
MouseButton::Left,
cx.listener(move |editor, _, cx| {
cx.stop_propagation();
editor
.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
cx,
)
.map(|task| task.detach_and_log_err(cx));
}),
)
// TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
.child(SharedString::from(action.lsp_action.title.clone()))
})
@@ -1589,6 +1588,7 @@ impl CodeActionsMenu {
.elevation_1(cx)
.px_2()
.py_1()
.track_scroll(self.scroll_handle.clone())
.with_width_from_item(
self.actions
.iter()
@@ -2320,6 +2320,7 @@ impl Editor {
self.blink_manager.update(cx, BlinkManager::pause_blinking);
cx.emit(EditorEvent::SelectionsChanged { local });
cx.emit(SearchEvent::MatchesInvalidated);
if self.selections.disjoint_anchors().len() == 1 {
cx.emit(SearchEvent::ActiveMatchChanged)
@@ -4354,11 +4355,11 @@ impl Editor {
style: &EditorStyle,
is_active: bool,
cx: &mut ViewContext<Self>,
) -> Option<IconButton<Self>> {
) -> Option<IconButton> {
if self.available_code_actions.is_some() {
Some(
IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(
|editor: &mut Editor, cx| {
IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(cx.listener(
|editor, e, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: true,
@@ -4366,7 +4367,7 @@ impl Editor {
cx,
);
},
),
)),
)
} else {
None
@@ -4381,7 +4382,7 @@ impl Editor {
line_height: Pixels,
gutter_margin: Pixels,
cx: &mut ViewContext<Self>,
) -> Vec<Option<IconButton<Self>>> {
) -> Vec<Option<IconButton>> {
fold_data
.iter()
.enumerate()
@@ -4394,15 +4395,15 @@ impl Editor {
FoldStatus::Foldable => ui::Icon::ChevronDown,
};
IconButton::new(ix as usize, icon)
.on_click(move |editor: &mut Editor, cx| match fold_status {
.on_click(cx.listener(move |editor, e, cx| match fold_status {
FoldStatus::Folded => {
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
}
FoldStatus::Foldable => {
editor.fold_at(&FoldAt { buffer_row }, cx);
}
})
.color(ui::TextColor::Muted)
}))
.color(ui::Color::Muted)
})
})
.flatten()
@@ -4422,7 +4423,7 @@ impl Editor {
cursor_position: DisplayPoint,
style: &EditorStyle,
cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, AnyElement<Editor>)> {
) -> Option<(DisplayPoint, AnyElement)> {
self.context_menu.read().as_ref().map(|menu| {
menu.render(
cursor_position,
@@ -7781,7 +7782,7 @@ impl Editor {
}
div()
.pl(cx.anchor_x)
.child(rename_editor.render_with(EditorElement::new(
.child(EditorElement::new(
&rename_editor,
EditorStyle {
background: cx.theme().system().transparent,
@@ -7789,10 +7790,12 @@ impl Editor {
text: text_style,
scrollbar_width: cx.editor_style.scrollbar_width,
syntax: cx.editor_style.syntax.clone(),
diagnostic_style:
cx.editor_style.diagnostic_style.clone(),
diagnostic_style: cx
.editor_style
.diagnostic_style
.clone(),
},
)))
))
.render_into_any()
}
}),
@@ -9388,7 +9391,7 @@ impl FocusableView for Editor {
}
}
impl Render<Self> for Editor {
impl Render for Editor {
type Element = EditorElement;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -9403,6 +9406,7 @@ impl Render<Self> for Editor {
font_style: FontStyle::Normal,
line_height: relative(1.).into(),
underline: None,
white_space: WhiteSpace::Normal,
},
EditorMode::AutoHeight { max_lines } => todo!(),
@@ -9416,6 +9420,7 @@ impl Render<Self> for Editor {
font_style: FontStyle::Normal,
line_height: relative(settings.buffer_line_height.value()),
underline: None,
white_space: WhiteSpace::Normal,
},
};
@@ -10002,10 +10007,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
.ml(cx.anchor_x)
}))
.cursor_pointer()
.on_click(move |_, _, cx| {
.on_click(cx.listener(move |_, _, cx| {
cx.write_to_clipboard(ClipboardItem::new(message.clone()));
})
.tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx))
}))
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx))
.render_into_any()
})
}
@@ -10124,49 +10129,50 @@ pub fn combine_syntax_and_fuzzy_match_highlights(
result
}
// pub fn styled_runs_for_code_label<'a>(
// label: &'a CodeLabel,
// syntax_theme: &'a theme::SyntaxTheme,
// ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
// let fade_out = HighlightStyle {
// fade_out: Some(0.35),
// ..Default::default()
// };
pub fn styled_runs_for_code_label<'a>(
label: &'a CodeLabel,
syntax_theme: &'a theme::SyntaxTheme,
) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
let fade_out = HighlightStyle {
fade_out: Some(0.35),
..Default::default()
};
// let mut prev_end = label.filter_range.end;
// label
// .runs
// .iter()
// .enumerate()
// .flat_map(move |(ix, (range, highlight_id))| {
// let style = if let Some(style) = highlight_id.style(syntax_theme) {
// style
// } else {
// return Default::default();
// };
// let mut muted_style = style;
// muted_style.highlight(fade_out);
let mut prev_end = label.filter_range.end;
label
.runs
.iter()
.enumerate()
.flat_map(move |(ix, (range, highlight_id))| {
let style = if let Some(style) = highlight_id.style(syntax_theme) {
style
} else {
return Default::default();
};
let mut muted_style = style;
muted_style.highlight(fade_out);
// let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
// if range.start >= label.filter_range.end {
// if range.start > prev_end {
// runs.push((prev_end..range.start, fade_out));
// }
// runs.push((range.clone(), muted_style));
// } else if range.end <= label.filter_range.end {
// runs.push((range.clone(), style));
// } else {
// runs.push((range.start..label.filter_range.end, style));
// runs.push((label.filter_range.end..range.end, muted_style));
// }
// prev_end = cmp::max(prev_end, range.end);
let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
if range.start >= label.filter_range.end {
if range.start > prev_end {
runs.push((prev_end..range.start, fade_out));
}
runs.push((range.clone(), muted_style));
} else if range.end <= label.filter_range.end {
runs.push((range.clone(), style));
} else {
runs.push((range.start..label.filter_range.end, style));
runs.push((label.filter_range.end..range.end, muted_style));
}
prev_end = cmp::max(prev_end, range.end);
// if ix + 1 == label.runs.len() && label.text.len() > prev_end {
// runs.push((prev_end..label.text.len(), fade_out));
// }
if ix + 1 == label.runs.len() && label.text.len() > prev_end {
runs.push((prev_end..label.text.len(), fade_out));
}
// runs
// })
runs
})
}
pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
let mut index = 0;
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -422,7 +422,7 @@ impl HoverState {
visible_rows: Range<u32>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
) -> Option<(DisplayPoint, Vec<AnyElement>)> {
todo!("old version below")
}
// // If there is a diagnostic, position the popovers based on that.
@@ -504,7 +504,7 @@ pub struct DiagnosticPopover {
}
impl DiagnosticPopover {
pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement {
todo!()
// enum PrimaryDiagnostic {}
+22 -26
View File
@@ -10,7 +10,7 @@ use futures::future::try_join_all;
use gpui::{
div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
ViewContext, VisualContext, WeakView,
ViewContext, VisualContext, WeakView, WindowContext,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -30,7 +30,7 @@ use std::{
};
use text::Selection;
use theme::{ActiveTheme, Theme};
use ui::{Label, TextColor};
use ui::{Color, Label};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
use workspace::{
@@ -584,7 +584,7 @@ impl Item for Editor {
Some(path.to_string_lossy().to_string().into())
}
fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
let theme = cx.theme();
AnyElement::new(
@@ -604,7 +604,7 @@ impl Item for Editor {
&description,
MAX_TAB_TITLE_LEN,
))
.color(TextColor::Muted),
.color(Color::Muted),
),
)
})),
@@ -761,7 +761,7 @@ impl Item for Editor {
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft { flex: None }
ToolbarItemLocation::PrimaryLeft
}
fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -907,17 +907,15 @@ impl SearchableItem for Editor {
type Match = Range<Anchor>;
fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
todo!()
// self.clear_background_highlights::<BufferSearchHighlights>(cx);
self.clear_background_highlights::<BufferSearchHighlights>(cx);
}
fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
todo!()
// self.highlight_background::<BufferSearchHighlights>(
// matches,
// |theme| theme.search.match_background,
// cx,
// );
self.highlight_background::<BufferSearchHighlights>(
matches,
|theme| theme.title_bar_background, // todo: update theme
cx,
);
}
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
@@ -952,22 +950,20 @@ impl SearchableItem for Editor {
matches: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>,
) {
todo!()
// self.unfold_ranges([matches[index].clone()], false, true, cx);
// let range = self.range_for_match(&matches[index]);
// self.change_selections(Some(Autoscroll::fit()), cx, |s| {
// s.select_ranges([range]);
// })
self.unfold_ranges([matches[index].clone()], false, true, cx);
let range = self.range_for_match(&matches[index]);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
})
}
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
todo!()
// self.unfold_ranges(matches.clone(), false, false, cx);
// let mut ranges = Vec::new();
// for m in &matches {
// ranges.push(self.range_for_match(&m))
// }
// self.change_selections(None, cx, |s| s.select_ranges(ranges));
self.unfold_ranges(matches.clone(), false, false, cx);
let mut ranges = Vec::new();
for m in &matches {
ranges.push(self.range_for_match(&m))
}
self.change_selections(None, cx, |s| s.select_ranges(ranges));
}
fn replace(
&mut self,
+3 -3
View File
@@ -117,8 +117,8 @@ impl FocusableView for FileFinder {
self.picker.focus_handle(cx)
}
}
impl Render<Self> for FileFinder {
type Element = Div<Self>;
impl Render for FileFinder {
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w_96().child(self.picker.clone())
@@ -530,7 +530,7 @@ impl FileFinderDelegate {
}
impl PickerDelegate for FileFinderDelegate {
type ListItem = Div<Picker<Self>>;
type ListItem = Div;
fn placeholder_text(&self) -> Arc<str> {
"Search project files...".into()
+6 -6
View File
@@ -5,7 +5,7 @@ use gpui::{
};
use text::{Bias, Point};
use theme::ActiveTheme;
use ui::{h_stack, v_stack, Label, StyledExt, TextColor};
use ui::{h_stack, v_stack, Color, Label, StyledExt};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::Workspace;
@@ -144,15 +144,15 @@ impl GoToLine {
}
}
impl Render<Self> for GoToLine {
type Element = Div<Self>;
impl Render for GoToLine {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.elevation_2(cx)
.key_context("GoToLine")
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::confirm))
.w_96()
.child(
v_stack()
@@ -176,7 +176,7 @@ impl Render<Self> for GoToLine {
.justify_between()
.px_2()
.py_1()
.child(Label::new(self.current_text.clone()).color(TextColor::Muted)),
.child(Label::new(self.current_text.clone()).color(Color::Muted)),
),
)
}
+1 -1
View File
@@ -424,7 +424,7 @@ impl AppContext {
/// Opens a new window with the given option and the root view returned by the given function.
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
/// functionality.
pub fn open_window<V: 'static + Render<V>>(
pub fn open_window<V: 'static + Render>(
&mut self,
options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
+3 -3
View File
@@ -115,7 +115,7 @@ impl AsyncAppContext {
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
) -> Result<WindowHandle<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
let app = self
.app
@@ -286,7 +286,7 @@ impl VisualContext for AsyncWindowContext {
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
self.window
.update(self, |_, cx| cx.build_view(build_view_state))
@@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
self.window
.update(self, |_, cx| cx.replace_root_view(build_view))
+16 -17
View File
@@ -1,8 +1,9 @@
use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent,
Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow,
View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
@@ -126,7 +127,7 @@ impl TestAppContext {
pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
where
F: FnOnce(&mut ViewContext<V>) -> V,
V: 'static + Render<V>,
V: 'static + Render,
{
let mut cx = self.app.borrow_mut();
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
@@ -143,7 +144,7 @@ impl TestAppContext {
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
where
F: FnOnce(&mut ViewContext<V>) -> V,
V: 'static + Render<V>,
V: 'static + Render,
{
let mut cx = self.app.borrow_mut();
let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
@@ -296,21 +297,19 @@ impl TestAppContext {
.unwrap()
}
pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
let (tx, rx) = futures::channel::mpsc::unbounded();
entity.update(self, move |_, cx: &mut ModelContext<T>| {
self.update(|cx| {
cx.observe(entity, {
let tx = tx.clone();
move |_, _, _| {
move |_, _| {
let _ = tx.unbounded_send(());
}
})
.detach();
cx.on_release(move |_, _| tx.close_channel()).detach();
cx.observe_release(entity, move |_, _| tx.close_channel())
.detach()
});
rx
}
@@ -569,7 +568,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
self.window
.update(self.cx, |_, cx| cx.build_view(build_view))
@@ -591,7 +590,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
self.window
.update(self.cx, |_, cx| cx.replace_root_view(build_view))
@@ -619,7 +618,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
}
impl AnyWindowHandle {
pub fn build_view<V: Render<V> + 'static>(
pub fn build_view<V: Render + 'static>(
&self,
cx: &mut TestAppContext,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
@@ -630,8 +629,8 @@ impl AnyWindowHandle {
pub struct EmptyView {}
impl Render<Self> for EmptyView {
type Element = Div<Self>;
impl Render for EmptyView {
type Element = Div;
fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
div()
+111 -164
View File
@@ -1,24 +1,25 @@
use crate::{
AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext,
WindowContext,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, marker::PhantomData};
use std::{any::Any, fmt::Debug};
pub trait Render<V: 'static>: 'static + Sized {
type Element: Element<V> + 'static;
pub trait Render: 'static + Sized {
type Element: Element + 'static;
fn render(&mut self, cx: &mut ViewContext<V>) -> Self::Element;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
}
pub trait RenderOnce<V: 'static>: Sized {
type Element: Element<V> + 'static;
pub trait RenderOnce: Sized {
type Element: Element + 'static;
fn element_id(&self) -> Option<ElementId>;
fn render_once(self) -> Self::Element;
fn render_into_any(self) -> AnyElement<V> {
fn render_into_any(self) -> AnyElement {
self.render_once().into_any()
}
@@ -26,9 +27,8 @@ pub trait RenderOnce<V: 'static>: Sized {
self,
origin: Point<Pixels>,
available_space: Size<T>,
view_state: &mut V,
cx: &mut ViewContext<V>,
f: impl FnOnce(&mut <Self::Element as Element<V>>::State, &mut ViewContext<V>) -> R,
cx: &mut WindowContext,
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
) -> R
where
T: Clone + Default + Debug + Into<AvailableSpace>,
@@ -39,13 +39,9 @@ pub trait RenderOnce<V: 'static>: Sized {
element: Some(element),
phase: ElementDrawPhase::Start,
};
let frame_state = DrawableElement::draw(
element,
origin,
available_space.map(Into::into),
view_state,
cx,
);
let frame_state =
DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
if let Some(mut frame_state) = frame_state {
f(&mut frame_state, cx)
@@ -61,7 +57,7 @@ pub trait RenderOnce<V: 'static>: Sized {
fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
where
Self: Sized,
U: RenderOnce<V>,
U: RenderOnce,
{
f(self)
}
@@ -87,70 +83,55 @@ pub trait RenderOnce<V: 'static>: Sized {
}
}
pub trait Element<V: 'static>: 'static + RenderOnce<V> {
pub trait Element: 'static + RenderOnce {
type State: 'static;
fn layout(
&mut self,
view_state: &mut V,
element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State);
fn paint(
self,
bounds: Bounds<Pixels>,
view_state: &mut V,
element_state: &mut Self::State,
cx: &mut ViewContext<V>,
);
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
fn into_any(self) -> AnyElement<V> {
fn into_any(self) -> AnyElement {
AnyElement::new(self)
}
}
pub trait Component<V: 'static>: 'static {
type Rendered: RenderOnce<V>;
pub trait Component: 'static {
type Rendered: RenderOnce;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered;
fn render(self, cx: &mut WindowContext) -> Self::Rendered;
}
pub struct CompositeElement<V, C> {
pub struct CompositeElement<C> {
component: Option<C>,
view_type: PhantomData<V>,
}
pub struct CompositeElementState<V: 'static, C: Component<V>> {
rendered_element: Option<<C::Rendered as RenderOnce<V>>::Element>,
rendered_element_state: <<C::Rendered as RenderOnce<V>>::Element as Element<V>>::State,
pub struct CompositeElementState<C: Component> {
rendered_element: Option<<C::Rendered as RenderOnce>::Element>,
rendered_element_state: <<C::Rendered as RenderOnce>::Element as Element>::State,
}
impl<V, C> CompositeElement<V, C> {
impl<C> CompositeElement<C> {
pub fn new(component: C) -> Self {
CompositeElement {
component: Some(component),
view_type: PhantomData,
}
}
}
impl<V: 'static, C: Component<V>> Element<V> for CompositeElement<V, C> {
type State = CompositeElementState<V, C>;
impl<C: Component> Element for CompositeElement<C> {
type State = CompositeElementState<C>;
fn layout(
&mut self,
view: &mut V,
state: Option<Self::State>,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let mut element = self
.component
.take()
.unwrap()
.render(view, cx)
.render_once();
let (layout_id, state) = element.layout(view, state.map(|s| s.rendered_element_state), cx);
let mut element = self.component.take().unwrap().render(cx).render_once();
let (layout_id, state) = element.layout(state.map(|s| s.rendered_element_state), cx);
let state = CompositeElementState {
rendered_element: Some(element),
rendered_element_state: state,
@@ -158,23 +139,16 @@ impl<V: 'static, C: Component<V>> Element<V> for CompositeElement<V, C> {
(layout_id, state)
}
fn paint(
self,
bounds: Bounds<Pixels>,
view: &mut V,
state: &mut Self::State,
cx: &mut ViewContext<V>,
) {
state.rendered_element.take().unwrap().paint(
bounds,
view,
&mut state.rendered_element_state,
cx,
);
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
state
.rendered_element
.take()
.unwrap()
.paint(bounds, &mut state.rendered_element_state, cx);
}
}
impl<V: 'static, C: Component<V>> RenderOnce<V> for CompositeElement<V, C> {
impl<C: Component> RenderOnce for CompositeElement<C> {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
@@ -189,10 +163,10 @@ impl<V: 'static, C: Component<V>> RenderOnce<V> for CompositeElement<V, C> {
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
pub trait ParentElement {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>;
fn child(mut self, child: impl RenderOnce<V>) -> Self
fn child(mut self, child: impl RenderOnce) -> Self
where
Self: Sized,
{
@@ -200,7 +174,7 @@ pub trait ParentElement<V: 'static> {
self
}
fn children(mut self, children: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
fn children(mut self, children: impl IntoIterator<Item = impl RenderOnce>) -> Self
where
Self: Sized,
{
@@ -213,26 +187,28 @@ pub trait ParentElement<V: 'static> {
}
}
trait ElementObject<V> {
trait ElementObject {
fn element_id(&self) -> Option<ElementId>;
fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
fn layout(&mut self, cx: &mut WindowContext) -> LayoutId;
fn paint(&mut self, cx: &mut WindowContext);
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> Size<Pixels>;
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
);
}
pub struct DrawableElement<V: 'static, E: Element<V>> {
pub struct DrawableElement<E: Element> {
element: Option<E>,
phase: ElementDrawPhase<E::State>,
}
@@ -253,7 +229,7 @@ enum ElementDrawPhase<S> {
}
/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
impl<V, E: Element<V>> DrawableElement<V, E> {
impl<E: Element> DrawableElement<E> {
fn new(element: E) -> Self {
DrawableElement {
element: Some(element),
@@ -265,18 +241,15 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
self.element.as_ref()?.element_id()
}
fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
{
let layout_id = cx.with_element_state(id, |element_state, cx| {
self.element
.as_mut()
.unwrap()
.layout(state, element_state, cx)
self.element.as_mut().unwrap().layout(element_state, cx)
});
(layout_id, None)
} else {
let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(state, None, cx);
let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx);
(layout_id, Some(frame_state))
};
@@ -287,7 +260,7 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
layout_id
}
fn paint(mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> Option<E::State> {
fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> {
match self.phase {
ElementDrawPhase::LayoutRequested {
layout_id,
@@ -304,7 +277,7 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
self.element
.take()
.unwrap()
.paint(bounds, view_state, &mut frame_state, cx);
.paint(bounds, &mut frame_state, cx);
Some(frame_state)
} else {
let element_id = self
@@ -315,12 +288,10 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
.expect("if we don't have frame state, we should have element state");
cx.with_element_state(element_id, |element_state, cx| {
let mut element_state = element_state.unwrap();
self.element.take().unwrap().paint(
bounds,
view_state,
&mut element_state,
cx,
);
self.element
.take()
.unwrap()
.paint(bounds, &mut element_state, cx);
((), element_state)
});
None
@@ -334,11 +305,10 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.layout(view_state, cx);
self.layout(cx);
}
let layout_id = match &mut self.phase {
@@ -376,22 +346,20 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> Option<E::State> {
self.measure(available_space, view_state, cx);
cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx))
self.measure(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
}
}
// impl<V: 'static, E: Element<V>> Element<V> for DrawableElement<V, E> {
// type State = <E::Element as Element<V>>::State;
// impl<V: 'static, E: Element> Element for DrawableElement<V, E> {
// type State = <E::Element as Element>::State;
// fn layout(
// &mut self,
// view_state: &mut V,
// element_state: Option<Self::State>,
// cx: &mut ViewContext<V>,
// cx: &mut WindowContext,
// ) -> (LayoutId, Self::State) {
// }
@@ -399,15 +367,14 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
// fn paint(
// self,
// bounds: Bounds<Pixels>,
// view_state: &mut V,
// element_state: &mut Self::State,
// cx: &mut ViewContext<V>,
// cx: &mut WindowContext,
// ) {
// todo!()
// }
// }
// impl<V: 'static, E: 'static + Element<V>> RenderOnce<V> for DrawableElement<V, E> {
// impl<V: 'static, E: 'static + Element> RenderOnce for DrawableElement<V, E> {
// type Element = Self;
// fn element_id(&self) -> Option<ElementId> {
@@ -419,81 +386,71 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
// }
// }
impl<V, E> ElementObject<V> for Option<DrawableElement<V, E>>
impl<E> ElementObject for Option<DrawableElement<E>>
where
E: Element<V>,
E: Element,
E::State: 'static,
{
fn element_id(&self) -> Option<ElementId> {
self.as_ref().unwrap().element_id()
}
fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
DrawableElement::layout(self.as_mut().unwrap(), view_state, cx)
fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
DrawableElement::layout(self.as_mut().unwrap(), cx)
}
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
DrawableElement::paint(self.take().unwrap(), view_state, cx);
fn paint(&mut self, cx: &mut WindowContext) {
DrawableElement::paint(self.take().unwrap(), cx);
}
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> Size<Pixels> {
DrawableElement::measure(self.as_mut().unwrap(), available_space, view_state, cx)
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
}
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) {
DrawableElement::draw(
self.take().unwrap(),
origin,
available_space,
view_state,
cx,
);
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
}
}
pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
pub struct AnyElement(Box<dyn ElementObject>);
impl<V: 'static> AnyElement<V> {
impl AnyElement {
pub fn new<E>(element: E) -> Self
where
V: 'static,
E: 'static + Element<V>,
E: 'static + Element,
E::State: Any,
{
AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject<V>>)
AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
}
pub fn element_id(&self) -> Option<ElementId> {
self.0.element_id()
}
pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
self.0.layout(view_state, cx)
pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
self.0.layout(cx)
}
pub fn paint(mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
self.0.paint(view_state, cx)
pub fn paint(mut self, cx: &mut WindowContext) {
self.0.paint(cx)
}
/// Initializes this element and performs layout within the given available space to determine its size.
pub fn measure(
&mut self,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> Size<Pixels> {
self.0.measure(available_space, view_state, cx)
self.0.measure(available_space, cx)
}
/// Initializes this element and performs layout in the available space, then paints it at the given origin.
@@ -501,43 +458,35 @@ impl<V: 'static> AnyElement<V> {
mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
view_state: &mut V,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) {
self.0.draw(origin, available_space, view_state, cx)
self.0.draw(origin, available_space, cx)
}
/// Converts this `AnyElement` into a trait object that can be stored and manipulated.
pub fn into_any(self) -> AnyElement<V> {
pub fn into_any(self) -> AnyElement {
AnyElement::new(self)
}
}
impl<V: 'static> Element<V> for AnyElement<V> {
impl Element for AnyElement {
type State = ();
fn layout(
&mut self,
view_state: &mut V,
_: Option<Self::State>,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let layout_id = self.layout(view_state, cx);
let layout_id = self.layout(cx);
(layout_id, ())
}
fn paint(
self,
_bounds: Bounds<Pixels>,
view_state: &mut V,
_: &mut Self::State,
cx: &mut ViewContext<V>,
) {
self.paint(view_state, cx);
fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
self.paint(cx);
}
}
impl<V: 'static> RenderOnce<V> for AnyElement<V> {
impl RenderOnce for AnyElement {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
@@ -549,13 +498,13 @@ impl<V: 'static> RenderOnce<V> for AnyElement<V> {
}
}
// impl<V, E, F> Element<V> for Option<F>
// impl<V, E, F> Element for Option<F>
// where
// V: 'static,
// E: Element<V>,
// F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
// E: Element,
// F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static,
// {
// type State = Option<AnyElement<V>>;
// type State = Option<AnyElement>;
// fn element_id(&self) -> Option<ElementId> {
// None
@@ -563,9 +512,8 @@ impl<V: 'static> RenderOnce<V> for AnyElement<V> {
// fn layout(
// &mut self,
// view_state: &mut V,
// _: Option<Self::State>,
// cx: &mut ViewContext<V>,
// cx: &mut WindowContext,
// ) -> (LayoutId, Self::State) {
// let render = self.take().unwrap();
// let mut element = (render)(view_state, cx).into_any();
@@ -576,19 +524,18 @@ impl<V: 'static> RenderOnce<V> for AnyElement<V> {
// fn paint(
// self,
// _bounds: Bounds<Pixels>,
// view_state: &mut V,
// rendered_element: &mut Self::State,
// cx: &mut ViewContext<V>,
// cx: &mut WindowContext,
// ) {
// rendered_element.take().unwrap().paint(view_state, cx);
// }
// }
// impl<V, E, F> RenderOnce<V> for Option<F>
// impl<V, E, F> RenderOnce for Option<F>
// where
// V: 'static,
// E: Element<V>,
// F: FnOnce(&mut V, &mut ViewContext<V>) -> E + 'static,
// E: Element,
// F: FnOnce(&mut V, &mut WindowContext) -> E + 'static,
// {
// type Element = Self;
File diff suppressed because it is too large Load Diff
+14 -19
View File
@@ -1,17 +1,17 @@
use crate::{
BorrowWindow, Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity,
LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext,
Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels,
RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
};
use futures::FutureExt;
use util::ResultExt;
pub struct Img<V: 'static> {
interactivity: Interactivity<V>,
pub struct Img {
interactivity: Interactivity,
uri: Option<SharedString>,
grayscale: bool,
}
pub fn img<V: 'static>() -> Img<V> {
pub fn img() -> Img {
Img {
interactivity: Interactivity::default(),
uri: None,
@@ -19,10 +19,7 @@ pub fn img<V: 'static>() -> Img<V> {
}
}
impl<V> Img<V>
where
V: 'static,
{
impl Img {
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
self.uri = Some(uri.into());
self
@@ -34,14 +31,13 @@ where
}
}
impl<V> Element<V> for Img<V> {
impl Element for Img {
type State = InteractiveElementState;
fn layout(
&mut self,
_view_state: &mut V,
element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None)
@@ -51,9 +47,8 @@ impl<V> Element<V> for Img<V> {
fn paint(
self,
bounds: Bounds<Pixels>,
_view_state: &mut V,
element_state: &mut Self::State,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) {
self.interactivity.paint(
bounds,
@@ -78,7 +73,7 @@ impl<V> Element<V> for Img<V> {
.log_err()
});
} else {
cx.spawn(|_, mut cx| async move {
cx.spawn(|mut cx| async move {
if image_future.await.ok().is_some() {
cx.on_next_frame(|cx| cx.notify());
}
@@ -91,7 +86,7 @@ impl<V> Element<V> for Img<V> {
}
}
impl<V: 'static> RenderOnce<V> for Img<V> {
impl RenderOnce for Img {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
@@ -103,14 +98,14 @@ impl<V: 'static> RenderOnce<V> for Img<V> {
}
}
impl<V> Styled for Img<V> {
impl Styled for Img {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
}
}
impl<V> InteractiveElement<V> for Img<V> {
fn interactivity(&mut self) -> &mut Interactivity<V> {
impl InteractiveElement for Img {
fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity
}
}
+13 -15
View File
@@ -3,15 +3,15 @@ use taffy::style::{Display, Position};
use crate::{
point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentElement, Pixels, Point,
RenderOnce, Size, Style,
RenderOnce, Size, Style, WindowContext,
};
pub struct OverlayState {
child_layout_ids: SmallVec<[LayoutId; 4]>,
}
pub struct Overlay<V> {
children: SmallVec<[AnyElement<V>; 2]>,
pub struct Overlay {
children: SmallVec<[AnyElement; 2]>,
anchor_corner: AnchorCorner,
fit_mode: OverlayFitMode,
// todo!();
@@ -21,7 +21,7 @@ pub struct Overlay<V> {
/// overlay gives you a floating element that will avoid overflowing the window bounds.
/// Its children should have no margin to avoid measurement issues.
pub fn overlay<V: 'static>() -> Overlay<V> {
pub fn overlay() -> Overlay {
Overlay {
children: SmallVec::new(),
anchor_corner: AnchorCorner::TopLeft,
@@ -30,7 +30,7 @@ pub fn overlay<V: 'static>() -> Overlay<V> {
}
}
impl<V> Overlay<V> {
impl Overlay {
/// Sets which corner of the overlay should be anchored to the current position.
pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
self.anchor_corner = anchor;
@@ -51,25 +51,24 @@ impl<V> Overlay<V> {
}
}
impl<V: 'static> ParentElement<V> for Overlay<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
impl ParentElement for Overlay {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
}
}
impl<V: 'static> Element<V> for Overlay<V> {
impl Element for Overlay {
type State = OverlayState;
fn layout(
&mut self,
view_state: &mut V,
_: Option<Self::State>,
cx: &mut crate::ViewContext<V>,
cx: &mut WindowContext,
) -> (crate::LayoutId, Self::State) {
let child_layout_ids = self
.children
.iter_mut()
.map(|child| child.layout(view_state, cx))
.map(|child| child.layout(cx))
.collect::<SmallVec<_>>();
let mut overlay_style = Style::default();
@@ -84,9 +83,8 @@ impl<V: 'static> Element<V> for Overlay<V> {
fn paint(
self,
bounds: crate::Bounds<crate::Pixels>,
view_state: &mut V,
element_state: &mut Self::State,
cx: &mut crate::ViewContext<V>,
cx: &mut WindowContext,
) {
if element_state.child_layout_ids.is_empty() {
return;
@@ -147,13 +145,13 @@ impl<V: 'static> Element<V> for Overlay<V> {
cx.with_element_offset(desired.origin - bounds.origin, |cx| {
for child in self.children {
child.paint(view_state, cx);
child.paint(cx);
}
})
}
}
impl<V: 'static> RenderOnce<V> for Overlay<V> {
impl RenderOnce for Overlay {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
+13 -19
View File
@@ -1,49 +1,43 @@
use crate::{
Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext,
LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
};
use util::ResultExt;
pub struct Svg<V: 'static> {
interactivity: Interactivity<V>,
pub struct Svg {
interactivity: Interactivity,
path: Option<SharedString>,
}
pub fn svg<V: 'static>() -> Svg<V> {
pub fn svg() -> Svg {
Svg {
interactivity: Interactivity::default(),
path: None,
}
}
impl<V> Svg<V> {
impl Svg {
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
self
}
}
impl<V> Element<V> for Svg<V> {
impl Element for Svg {
type State = InteractiveElementState;
fn layout(
&mut self,
_view_state: &mut V,
element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None)
})
}
fn paint(
self,
bounds: Bounds<Pixels>,
_view_state: &mut V,
element_state: &mut Self::State,
cx: &mut ViewContext<V>,
) where
fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
where
Self: Sized,
{
self.interactivity
@@ -55,7 +49,7 @@ impl<V> Element<V> for Svg<V> {
}
}
impl<V: 'static> RenderOnce<V> for Svg<V> {
impl RenderOnce for Svg {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
@@ -67,14 +61,14 @@ impl<V: 'static> RenderOnce<V> for Svg<V> {
}
}
impl<V> Styled for Svg<V> {
impl Styled for Svg {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
}
}
impl<V> InteractiveElement<V> for Svg<V> {
fn interactivity(&mut self) -> &mut Interactivity<V> {
impl InteractiveElement for Svg {
fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity
}
}
+41 -147
View File
@@ -1,6 +1,6 @@
use crate::{
BorrowWindow, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size,
TextRun, ViewContext, WindowContext, WrappedLine,
Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun,
WhiteSpace, WindowContext, WrappedLine,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
@@ -8,32 +8,25 @@ use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc, sync::Arc};
use util::ResultExt;
impl<V: 'static> Element<V> for &'static str {
impl Element for &'static str {
type State = TextState;
fn layout(
&mut self,
_: &mut V,
_: Option<Self::State>,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let mut state = TextState::default();
let layout_id = state.layout(SharedString::from(*self), None, cx);
(layout_id, state)
}
fn paint(
self,
bounds: Bounds<Pixels>,
_: &mut V,
state: &mut TextState,
cx: &mut ViewContext<V>,
) {
fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
state.paint(bounds, self, cx)
}
}
impl<V: 'static> RenderOnce<V> for &'static str {
impl RenderOnce for &'static str {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
@@ -45,37 +38,30 @@ impl<V: 'static> RenderOnce<V> for &'static str {
}
}
impl<V: 'static> Element<V> for SharedString {
impl Element for SharedString {
type State = TextState;
fn layout(
&mut self,
_: &mut V,
_: Option<Self::State>,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let mut state = TextState::default();
let layout_id = state.layout(self.clone(), None, cx);
(layout_id, state)
}
fn paint(
self,
bounds: Bounds<Pixels>,
_: &mut V,
state: &mut TextState,
cx: &mut ViewContext<V>,
) {
fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
let text_str: &str = self.as_ref();
state.paint(bounds, text_str, cx)
}
}
impl<V: 'static> RenderOnce<V> for SharedString {
impl RenderOnce for SharedString {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(self.clone().into())
None
}
fn render_once(self) -> Self::Element {
@@ -102,110 +88,25 @@ impl StyledText {
}
}
impl<V: 'static> Element<V> for StyledText {
impl Element for StyledText {
type State = TextState;
fn layout(
&mut self,
_view: &mut V,
element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
_: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let element_state = element_state.unwrap_or_default();
let text_system = cx.text_system().clone();
let text_style = cx.text_style();
let font_size = text_style.font_size.to_pixels(cx.rem_size());
let line_height = text_style
.line_height
.to_pixels(font_size.into(), cx.rem_size());
let text = self.text.clone();
let rem_size = cx.rem_size();
let runs = if let Some(runs) = self.runs.take() {
runs
} else {
vec![text_style.to_run(text.len())]
};
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone();
move |known_dimensions, available_space| {
let wrap_width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
_ => None,
});
if let Some(text_state) = element_state.0.lock().as_ref() {
if text_state.size.is_some()
&& (wrap_width.is_none() || wrap_width == text_state.wrap_width)
{
return text_state.size.unwrap();
}
}
let Some(lines) = text_system
.shape_text(
&text,
font_size,
&runs[..],
wrap_width, // Wrap if we know the width.
)
.log_err()
else {
element_state.lock().replace(TextStateInner {
lines: Default::default(),
line_height,
wrap_width,
size: Some(Size::default()),
});
return Size::default();
};
let mut size: Size<Pixels> = Size::default();
for line in &lines {
let line_size = line.size(line_height);
size.height += line_size.height;
size.width = size.width.max(line_size.width);
}
element_state.lock().replace(TextStateInner {
lines,
line_height,
wrap_width,
size: Some(size),
});
size
}
});
(layout_id, element_state)
let mut state = TextState::default();
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
(layout_id, state)
}
fn paint(
self,
bounds: Bounds<Pixels>,
_: &mut V,
element_state: &mut Self::State,
cx: &mut ViewContext<V>,
) {
let element_state = element_state.lock();
let element_state = element_state
.as_ref()
.ok_or_else(|| anyhow!("measurement has not been performed on {}", &self.text))
.unwrap();
let line_height = element_state.line_height;
let mut line_origin = bounds.origin;
for line in &element_state.lines {
line.paint(line_origin, line_height, cx).log_err();
line_origin.y += line.size(line_height).height;
}
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
state.paint(bounds, &self.text, cx)
}
}
impl<V: 'static> RenderOnce<V> for StyledText {
impl RenderOnce for StyledText {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
@@ -258,10 +159,14 @@ impl TextState {
let element_state = self.clone();
move |known_dimensions, available_space| {
let wrap_width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
_ => None,
});
let wrap_width = if text_style.white_space == WhiteSpace::Normal {
known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
_ => None,
})
} else {
None
};
if let Some(text_state) = element_state.0.lock().as_ref() {
if text_state.size.is_some()
@@ -273,10 +178,7 @@ impl TextState {
let Some(lines) = text_system
.shape_text(
&text,
font_size,
&runs[..],
wrap_width, // Wrap if we know the width.
&text, font_size, &runs, wrap_width, // Wrap if we know the width.
)
.log_err()
else {
@@ -293,7 +195,7 @@ impl TextState {
for line in &lines {
let line_size = line.size(line_height);
size.height += line_size.height;
size.width = size.width.max(line_size.width);
size.width = size.width.max(line_size.width).ceil();
}
element_state.lock().replace(TextStateInner {
@@ -327,7 +229,7 @@ impl TextState {
}
struct InteractiveText {
id: ElementId,
element_id: ElementId,
text: StyledText,
}
@@ -336,28 +238,27 @@ struct InteractiveTextState {
clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
}
impl<V: 'static> Element<V> for InteractiveText {
impl Element for InteractiveText {
type State = InteractiveTextState;
fn layout(
&mut self,
view_state: &mut V,
element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
if let Some(InteractiveTextState {
text_state,
clicked_range_ixs,
}) = element_state
}) = state
{
let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx);
let (layout_id, text_state) = self.text.layout(Some(text_state), cx);
let element_state = InteractiveTextState {
text_state,
clicked_range_ixs,
};
(layout_id, element_state)
} else {
let (layout_id, text_state) = self.text.layout(view_state, None, cx);
let (layout_id, text_state) = self.text.layout(None, cx);
let element_state = InteractiveTextState {
text_state,
clicked_range_ixs: Rc::default(),
@@ -366,23 +267,16 @@ impl<V: 'static> Element<V> for InteractiveText {
}
}
fn paint(
self,
bounds: Bounds<Pixels>,
view_state: &mut V,
element_state: &mut Self::State,
cx: &mut ViewContext<V>,
) {
self.text
.paint(bounds, view_state, &mut element_state.text_state, cx)
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
self.text.paint(bounds, &mut state.text_state, cx)
}
}
impl<V: 'static> RenderOnce<V> for InteractiveText {
impl RenderOnce for InteractiveText {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(self.id.clone())
Some(self.element_id.clone())
}
fn render_once(self) -> Self::Element {
+45 -55
View File
@@ -1,7 +1,7 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Element, ElementId,
InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, Point,
RenderOnce, Size, StyleRefinement, Styled, ViewContext,
point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement,
InteractiveElementState, Interactivity, LayoutId, Pixels, Point, Render, RenderOnce, Size,
StyleRefinement, Styled, View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -10,52 +10,51 @@ use taffy::style::Overflow;
/// uniform_list provides lazy rendering for a set of items that are of uniform height.
/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
/// uniform_list will only render the visibile subset of items.
pub fn uniform_list<I, V, E>(
pub fn uniform_list<I, R, V>(
view: View<V>,
id: I,
item_count: usize,
f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<E>,
) -> UniformList<V>
f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
) -> UniformList
where
I: Into<ElementId>,
V: 'static,
E: Element<V>,
R: RenderOnce,
V: Render,
{
let id = id.into();
let mut style = StyleRefinement::default();
style.overflow.y = Some(Overflow::Hidden);
let mut base_style = StyleRefinement::default();
base_style.overflow.y = Some(Overflow::Scroll);
let render_range = move |range, cx: &mut WindowContext| {
view.update(cx, |this, cx| {
f(this, range, cx)
.into_iter()
.map(|component| component.render_into_any())
.collect()
})
};
UniformList {
id: id.clone(),
style,
item_count,
item_to_measure_index: 0,
render_items: Box::new(move |view, visible_range, cx| {
f(view, visible_range, cx)
.into_iter()
.map(|component| component.into_any())
.collect()
}),
render_items: Box::new(render_range),
interactivity: Interactivity {
element_id: Some(id.into()),
base_style,
..Default::default()
},
scroll_handle: None,
}
}
pub struct UniformList<V: 'static> {
pub struct UniformList {
id: ElementId,
style: StyleRefinement,
item_count: usize,
item_to_measure_index: usize,
render_items: Box<
dyn for<'a> Fn(
&'a mut V,
Range<usize>,
&'a mut ViewContext<V>,
) -> SmallVec<[AnyElement<V>; 64]>,
>,
interactivity: Interactivity<V>,
render_items:
Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
interactivity: Interactivity,
scroll_handle: Option<UniformListScrollHandle>,
}
@@ -89,9 +88,9 @@ impl UniformListScrollHandle {
}
}
impl<V: 'static> Styled for UniformList<V> {
impl Styled for UniformList {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
&mut self.interactivity.base_style
}
}
@@ -101,25 +100,24 @@ pub struct UniformListState {
item_size: Size<Pixels>,
}
impl<V: 'static> Element<V> for UniformList<V> {
impl Element for UniformList {
type State = UniformListState;
fn layout(
&mut self,
view_state: &mut V,
element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let max_items = self.item_count;
let rem_size = cx.rem_size();
let item_size = element_state
let item_size = state
.as_ref()
.map(|s| s.item_size)
.unwrap_or_else(|| self.measure_item(view_state, None, cx));
.unwrap_or_else(|| self.measure_item(None, cx));
let (layout_id, interactive) =
self.interactivity
.layout(element_state.map(|s| s.interactive), cx, |style, cx| {
.layout(state.map(|s| s.interactive), cx, |style, cx| {
cx.request_measured_layout(
style,
rem_size,
@@ -157,9 +155,8 @@ impl<V: 'static> Element<V> for UniformList<V> {
fn paint(
self,
bounds: Bounds<crate::Pixels>,
view_state: &mut V,
element_state: &mut Self::State,
cx: &mut ViewContext<V>,
cx: &mut WindowContext,
) {
let style =
self.interactivity
@@ -185,9 +182,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
.get_or_insert_with(Rc::default)
.clone();
let item_height = self
.measure_item(view_state, Some(padded_bounds.size.width), cx)
.height;
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
self.interactivity.paint(
bounds,
@@ -229,7 +224,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
self.item_count,
);
let items = (self.render_items)(view_state, visible_range.clone(), cx);
let items = (self.render_items)(visible_range.clone(), cx);
cx.with_z_index(1, |cx| {
for (item, ix) in items.into_iter().zip(visible_range) {
let item_origin = padded_bounds.origin
@@ -238,7 +233,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, view_state, cx);
item.draw(item_origin, available_space, cx);
}
});
}
@@ -248,7 +243,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
}
}
impl<V> RenderOnce<V> for UniformList<V> {
impl RenderOnce for UniformList {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
@@ -260,24 +255,19 @@ impl<V> RenderOnce<V> for UniformList<V> {
}
}
impl<V> UniformList<V> {
impl UniformList {
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
self.item_to_measure_index = item_index.unwrap_or(0);
self
}
fn measure_item(
&self,
view_state: &mut V,
list_width: Option<Pixels>,
cx: &mut ViewContext<V>,
) -> Size<Pixels> {
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
if self.item_count == 0 {
return Size::default();
}
let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
let mut items = (self.render_items)(view_state, item_ix..item_ix + 1, cx);
let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
let mut item_to_measure = items.pop().unwrap();
let available_space = size(
list_width.map_or(AvailableSpace::MinContent, |width| {
@@ -285,7 +275,7 @@ impl<V> UniformList<V> {
}),
AvailableSpace::MinContent,
);
item_to_measure.measure(available_space, view_state, cx)
item_to_measure.measure(available_space, cx)
}
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
@@ -294,8 +284,8 @@ impl<V> UniformList<V> {
}
}
impl<V> InteractiveElement<V> for UniformList<V> {
fn interactivity(&mut self) -> &mut crate::Interactivity<V> {
impl InteractiveElement for UniformList {
fn interactivity(&mut self) -> &mut crate::Interactivity {
&mut self.interactivity
}
}
+2 -2
View File
@@ -121,7 +121,7 @@ pub trait VisualContext: Context {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>;
V: 'static + Render;
fn update_view<V: 'static, R>(
&mut self,
@@ -134,7 +134,7 @@ pub trait VisualContext: Context {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>;
V: 'static + Render;
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
where
+5 -3
View File
@@ -1,4 +1,6 @@
use crate::{AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext};
use crate::{
AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext,
};
use std::ops::Range;
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
@@ -43,9 +45,9 @@ pub struct ElementInputHandler<V> {
impl<V: 'static> ElementInputHandler<V> {
/// Used in [Element::paint] with the element's bounds and a view context for its
/// containing view.
pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {
pub fn new(element_bounds: Bounds<Pixels>, view: View<V>, cx: &mut WindowContext) -> Self {
ElementInputHandler {
view: cx.view().clone(),
view,
element_bounds,
cx: cx.to_async(),
}
+24 -14
View File
@@ -1,5 +1,6 @@
use crate::{
div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, RenderOnce,
ViewContext,
};
use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
@@ -63,24 +64,24 @@ pub struct Drag<S, R, V, E>
where
R: Fn(&mut V, &mut ViewContext<V>) -> E,
V: 'static,
E: Element<()>,
E: RenderOnce,
{
pub state: S,
pub render_drag_handle: R,
view_type: PhantomData<V>,
view_element_types: PhantomData<(V, E)>,
}
impl<S, R, V, E> Drag<S, R, V, E>
where
R: Fn(&mut V, &mut ViewContext<V>) -> E,
V: 'static,
E: Element<()>,
E: Element,
{
pub fn new(state: S, render_drag_handle: R) -> Self {
Drag {
state,
render_drag_handle,
view_type: PhantomData,
view_element_types: Default::default(),
}
}
}
@@ -192,8 +193,8 @@ impl Deref for MouseExitEvent {
#[derive(Debug, Clone, Default)]
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
impl Render<Self> for ExternalPaths {
type Element = Div<Self>;
impl Render for ExternalPaths {
type Element = Div;
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
div() // Intentionally left empty because the platform will render icons for the dragged files
@@ -286,7 +287,7 @@ pub struct FocusEvent {
mod test {
use crate::{
self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke,
ParentElement, Render, Stateful, TestAppContext, VisualContext,
ParentElement, Render, RenderOnce, Stateful, TestAppContext, VisualContext,
};
struct TestView {
@@ -297,16 +298,25 @@ mod test {
actions!(TestAction);
impl Render<Self> for TestView {
type Element = Stateful<Self, Div<Self>>;
impl Render for TestView {
type Element = Stateful<Div>;
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child(
div()
.key_context("parent")
.on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
.on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
.child(div().key_context("nested").track_focus(&self.focus_handle)),
.on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
.on_action(
cx.listener(|this: &mut TestView, _: &TestAction, _| {
this.saw_action = true
}),
)
.child(
div()
.key_context("nested")
.track_focus(&self.focus_handle)
.render_once(),
),
)
}
}
+11 -2
View File
@@ -2,7 +2,7 @@ use crate::{
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext,
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
};
use refineable::{Cascade, Refineable};
use smallvec::SmallVec;
@@ -128,6 +128,13 @@ pub struct BoxShadow {
pub spread_radius: Pixels,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum WhiteSpace {
#[default]
Normal,
Nowrap,
}
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug)]
pub struct TextStyle {
@@ -139,6 +146,7 @@ pub struct TextStyle {
pub font_weight: FontWeight,
pub font_style: FontStyle,
pub underline: Option<UnderlineStyle>,
pub white_space: WhiteSpace,
}
impl Default for TextStyle {
@@ -152,6 +160,7 @@ impl Default for TextStyle {
font_weight: FontWeight::default(),
font_style: FontStyle::default(),
underline: None,
white_space: WhiteSpace::Normal,
}
}
}
@@ -313,7 +322,7 @@ impl Style {
}
/// Paints the background of an element styled with this style.
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
let rem_size = cx.rem_size();
cx.with_z_index(0, |cx| {
+19 -1
View File
@@ -1,7 +1,7 @@
use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
SharedString, StyleRefinement, Visibility,
SharedString, StyleRefinement, Visibility, WhiteSpace,
};
use crate::{BoxShadow, TextStyleRefinement};
use smallvec::{smallvec, SmallVec};
@@ -101,6 +101,24 @@ pub trait Styled: Sized {
self
}
/// Sets the whitespace of the element to `normal`.
/// [Docs](https://tailwindcss.com/docs/whitespace#normal)
fn whitespace_normal(mut self) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.white_space = Some(WhiteSpace::Normal);
self
}
/// Sets the whitespace of the element to `nowrap`.
/// [Docs](https://tailwindcss.com/docs/whitespace#nowrap)
fn whitespace_nowrap(mut self) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.white_space = Some(WhiteSpace::Nowrap);
self
}
/// Sets the flex direction of the element to `column`.
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
fn flex_col(mut self) -> Self {
+58 -124
View File
@@ -6,7 +6,7 @@ use crate::{
};
use anyhow::{Context, Result};
use std::{
any::{Any, TypeId},
any::TypeId,
hash::{Hash, Hasher},
};
@@ -59,15 +59,15 @@ impl<V: 'static> View<V> {
self.model.read(cx)
}
pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
where
E: 'static + Element<V>,
{
RenderViewWith {
view: self.clone(),
element: Some(component),
}
}
// pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
// where
// E: 'static + Element,
// {
// RenderViewWith {
// view: self.clone(),
// element: Some(component),
// }
// }
pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
where
@@ -77,6 +77,24 @@ impl<V: 'static> View<V> {
}
}
impl<V: Render> Element for View<V> {
type State = Option<AnyElement>;
fn layout(
&mut self,
_state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let mut element = self.update(cx, |view, cx| view.render(cx).into_any());
let layout_id = element.layout(cx);
(layout_id, Some(element))
}
fn paint(self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
element.take().unwrap().paint(cx);
}
}
impl<V> Clone for View<V> {
fn clone(&self) -> Self {
Self {
@@ -151,8 +169,8 @@ impl<V> Eq for WeakView<V> {}
#[derive(Clone, Debug)]
pub struct AnyView {
model: AnyModel,
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
paint: fn(&AnyView, Box<dyn Any>, &mut WindowContext),
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
paint: fn(&AnyView, AnyElement, &mut WindowContext),
}
impl AnyView {
@@ -199,7 +217,7 @@ impl AnyView {
}
}
impl<V: 'static + Render<V>> From<View<V>> for AnyView {
impl<V: Render> From<View<V>> for AnyView {
fn from(value: View<V>) -> Self {
AnyView {
model: value.model.into_any(),
@@ -209,36 +227,24 @@ impl<V: 'static + Render<V>> From<View<V>> for AnyView {
}
}
impl<V: 'static + Render<V>, ParentV: 'static> Element<ParentV> for View<V> {
type State = Option<AnyElement<V>>;
impl Element for AnyView {
type State = Option<AnyElement>;
fn layout(
&mut self,
_parent_view: &mut ParentV,
_state: Option<Self::State>,
cx: &mut ViewContext<ParentV>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
self.update(cx, |view, cx| {
let mut element = view.render(cx).into_any();
let layout_id = element.layout(view, cx);
(layout_id, Some(element))
})
let (layout_id, state) = (self.layout)(self, cx);
(layout_id, Some(state))
}
fn paint(
self,
_: Bounds<Pixels>,
_parent: &mut ParentV,
element: &mut Self::State,
cx: &mut ViewContext<ParentV>,
) {
self.update(cx, |view, cx| {
element.take().unwrap().paint(view, cx);
});
fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
(self.paint)(&self, state.take().unwrap(), cx)
}
}
impl<V: 'static + Render<V>, ParentV: 'static> RenderOnce<ParentV> for View<V> {
impl<V: 'static + Render> RenderOnce for View<V> {
type Element = View<V>;
fn element_id(&self) -> Option<ElementId> {
@@ -250,31 +256,7 @@ impl<V: 'static + Render<V>, ParentV: 'static> RenderOnce<ParentV> for View<V> {
}
}
impl<V: 'static> Element<V> for AnyView {
type State = Option<Box<dyn Any>>;
fn layout(
&mut self,
_view_state: &mut V,
_element_state: Option<Self::State>,
cx: &mut ViewContext<V>,
) -> (LayoutId, Self::State) {
let (layout_id, rendered_element) = (self.layout)(self, cx);
(layout_id, Some(rendered_element))
}
fn paint(
mut self,
_bounds: Bounds<Pixels>,
_view_state: &mut V,
rendered_element: &mut Self::State,
cx: &mut ViewContext<V>,
) {
(self.paint)(&mut self, rendered_element.take().unwrap(), cx)
}
}
impl<ParentV: 'static> RenderOnce<ParentV> for AnyView {
impl RenderOnce for AnyView {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
@@ -288,8 +270,8 @@ impl<ParentV: 'static> RenderOnce<ParentV> for AnyView {
pub struct AnyWeakView {
model: AnyWeakModel,
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
paint: fn(&AnyView, Box<dyn Any>, &mut WindowContext),
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
paint: fn(&AnyView, AnyElement, &mut WindowContext),
}
impl AnyWeakView {
@@ -303,7 +285,7 @@ impl AnyWeakView {
}
}
impl<V: 'static + Render<V>> From<WeakView<V>> for AnyWeakView {
impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
fn from(view: WeakView<V>) -> Self {
Self {
model: view.model.into(),
@@ -313,88 +295,40 @@ impl<V: 'static + Render<V>> From<WeakView<V>> for AnyWeakView {
}
}
pub struct RenderViewWith<E, V> {
view: View<V>,
element: Option<E>,
}
impl<E, ParentV, V> Element<ParentV> for RenderViewWith<E, V>
impl<T, E> Render for T
where
E: 'static + Element<V>,
ParentV: 'static,
V: 'static,
T: 'static + FnMut(&mut WindowContext) -> E,
E: 'static + Send + Element,
{
type State = Option<AnyElement<V>>;
type Element = E;
fn layout(
&mut self,
_: &mut ParentV,
_: Option<Self::State>,
cx: &mut ViewContext<ParentV>,
) -> (LayoutId, Self::State) {
self.view.update(cx, |view, cx| {
let mut element = self.element.take().unwrap().into_any();
let layout_id = element.layout(view, cx);
(layout_id, Some(element))
})
}
fn paint(
self,
_: Bounds<Pixels>,
_: &mut ParentV,
element: &mut Self::State,
cx: &mut ViewContext<ParentV>,
) {
self.view
.update(cx, |view, cx| element.take().unwrap().paint(view, cx))
}
}
impl<E, V, ParentV> RenderOnce<ParentV> for RenderViewWith<E, V>
where
E: 'static + Element<V>,
V: 'static,
ParentV: 'static,
{
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
self.element.as_ref().unwrap().element_id()
}
fn render_once(self) -> Self::Element {
self
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
(self)(cx)
}
}
mod any_view {
use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext};
use std::any::Any;
use crate::{AnyElement, AnyView, BorrowWindow, Element, LayoutId, Render, WindowContext};
pub(crate) fn layout<V: 'static + Render<V>>(
pub(crate) fn layout<V: 'static + Render>(
view: &AnyView,
cx: &mut WindowContext,
) -> (LayoutId, Box<dyn Any>) {
) -> (LayoutId, AnyElement) {
cx.with_element_id(Some(view.model.entity_id), |cx| {
let view = view.clone().downcast::<V>().unwrap();
view.update(cx, |view, cx| {
let mut element = AnyElement::new(view.render(cx));
let layout_id = element.layout(view, cx);
(layout_id, Box::new(element) as Box<dyn Any>)
})
let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
let layout_id = element.layout(cx);
(layout_id, element)
})
}
pub(crate) fn paint<V: 'static + Render<V>>(
pub(crate) fn paint<V: 'static + Render>(
view: &AnyView,
element: Box<dyn Any>,
element: AnyElement,
cx: &mut WindowContext,
) {
cx.with_element_id(Some(view.model.entity_id), |cx| {
let view = view.clone().downcast::<V>().unwrap();
let element = element.downcast::<AnyElement<V>>().unwrap();
view.update(cx, |view, cx| element.paint(view, cx))
element.paint(cx);
})
}
}
+96 -64
View File
@@ -187,7 +187,7 @@ impl Drop for FocusHandle {
/// FocusableView allows users of your view to easily
/// focus it (using cx.focus_view(view))
pub trait FocusableView: 'static + Render<Self> {
pub trait FocusableView: 'static + Render {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
}
@@ -1436,6 +1436,82 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.bindings_for_action(action)
}
pub fn listener_for<V: Render, E>(
&self,
view: &View<V>,
f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
) -> impl Fn(&E, &mut WindowContext) + 'static {
let view = view.downgrade();
move |e: &E, cx: &mut WindowContext| {
view.update(cx, |view, cx| f(view, e, cx)).ok();
}
}
pub fn constructor_for<V: Render, R>(
&self,
view: &View<V>,
f: impl Fn(&mut V, &mut ViewContext<V>) -> R + 'static,
) -> impl Fn(&mut WindowContext) -> R + 'static {
let view = view.clone();
move |cx: &mut WindowContext| view.update(cx, |view, cx| f(view, cx))
}
//========== ELEMENT RELATED FUNCTIONS ===========
pub fn with_key_dispatch<R>(
&mut self,
context: KeyContext,
focus_handle: Option<FocusHandle>,
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
) -> R {
let window = &mut self.window;
window
.current_frame
.dispatch_tree
.push_node(context.clone());
if let Some(focus_handle) = focus_handle.as_ref() {
window
.current_frame
.dispatch_tree
.make_focusable(focus_handle.id);
}
let result = f(focus_handle, self);
self.window.current_frame.dispatch_tree.pop_node();
result
}
/// Register a focus listener for the current frame only. It will be cleared
/// on the next frame render. You should use this method only from within elements,
/// and we may want to enforce that better via a different context type.
// todo!() Move this to `FrameContext` to emphasize its individuality?
pub fn on_focus_changed(
&mut self,
listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static,
) {
self.window
.current_frame
.focus_listeners
.push(Box::new(move |event, cx| {
listener(event, cx);
}));
}
/// Set an input handler, such as [ElementInputHandler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions.
pub fn handle_input(
&mut self,
focus_handle: &FocusHandle,
input_handler: impl PlatformInputHandler,
) {
if focus_handle.is_focused(self) {
self.window
.platform_window
.set_input_handler(Box::new(input_handler));
}
}
}
impl Context for WindowContext<'_> {
@@ -1520,7 +1596,7 @@ impl VisualContext for WindowContext<'_> {
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
let slot = self.app.entities.reserve();
let view = View {
@@ -1559,7 +1635,7 @@ impl VisualContext for WindowContext<'_> {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render<V>,
V: 'static + Render,
{
let slot = self.app.entities.reserve();
let view = View {
@@ -1617,6 +1693,10 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
self.borrow_mut()
}
fn app(&self) -> &AppContext {
self.borrow()
}
fn window(&self) -> &Window {
self.borrow()
}
@@ -2124,49 +2204,6 @@ impl<'a, V: 'static> ViewContext<'a, V> {
)
}
/// Register a focus listener for the current frame only. It will be cleared
/// on the next frame render. You should use this method only from within elements,
/// and we may want to enforce that better via a different context type.
// todo!() Move this to `FrameContext` to emphasize its individuality?
pub fn on_focus_changed(
&mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) {
let handle = self.view().downgrade();
self.window
.current_frame
.focus_listeners
.push(Box::new(move |event, cx| {
handle
.update(cx, |view, cx| listener(view, event, cx))
.log_err();
}));
}
pub fn with_key_dispatch<R>(
&mut self,
context: KeyContext,
focus_handle: Option<FocusHandle>,
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
) -> R {
let window = &mut self.window;
window
.current_frame
.dispatch_tree
.push_node(context.clone());
if let Some(focus_handle) = focus_handle.as_ref() {
window
.current_frame
.dispatch_tree
.make_focusable(focus_handle.id);
}
let result = f(focus_handle, self);
self.window.current_frame.dispatch_tree.pop_node();
result
}
pub fn spawn<Fut, R>(
&mut self,
f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2243,21 +2280,6 @@ impl<'a, V: 'static> ViewContext<'a, V> {
});
}
/// Set an input handler, such as [ElementInputHandler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions.
pub fn handle_input(
&mut self,
focus_handle: &FocusHandle,
input_handler: impl PlatformInputHandler,
) {
if focus_handle.is_focused(self) {
self.window
.platform_window
.set_input_handler(Box::new(input_handler));
}
}
pub fn emit<Evt>(&mut self, event: Evt)
where
Evt: 'static,
@@ -2284,6 +2306,16 @@ impl<'a, V: 'static> ViewContext<'a, V> {
{
self.defer(|_, cx| cx.emit(Manager::Dismiss))
}
pub fn listener<E>(
&self,
f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
) -> impl Fn(&E, &mut WindowContext) + 'static {
let view = self.view().downgrade();
move |e: &E, cx: &mut WindowContext| {
view.update(cx, |view, cx| f(view, e, cx)).ok();
}
}
}
impl<V> Context for ViewContext<'_, V> {
@@ -2335,7 +2367,7 @@ impl<V> Context for ViewContext<'_, V> {
}
impl<V: 'static> VisualContext for ViewContext<'_, V> {
fn build_view<W: Render<W> + 'static>(
fn build_view<W: Render + 'static>(
&mut self,
build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
) -> Self::Result<View<W>> {
@@ -2355,7 +2387,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> {
build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
) -> Self::Result<View<W>>
where
W: 'static + Render<W>,
W: 'static + Render,
{
self.window_cx.replace_root_view(build_view)
}
@@ -2400,7 +2432,7 @@ pub struct WindowHandle<V> {
state_type: PhantomData<V>,
}
impl<V: 'static + Render<V>> WindowHandle<V> {
impl<V: 'static + Render> WindowHandle<V> {
pub fn new(id: WindowId) -> Self {
WindowHandle {
any_handle: AnyWindowHandle {
+4 -41
View File
@@ -1,37 +1,17 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput};
use syn::{parse_macro_input, DeriveInput};
pub fn derive_render_once(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = &ast.ident;
let mut trait_generics = ast.generics.clone();
let view_type = if let Some(view_type) = specified_view_type(&ast) {
quote! { #view_type }
} else {
if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| {
if let syn::GenericParam::Type(type_param) = param {
Some(type_param.ident.clone())
} else {
None
}
}) {
quote! { #first_type_param }
} else {
trait_generics.params.push(parse_quote! { V: 'static });
quote! { V }
}
};
let (impl_generics, _, where_clause) = trait_generics.split_for_impl();
let (_, type_generics, _) = ast.generics.split_for_impl();
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
let gen = quote! {
impl #impl_generics gpui::RenderOnce<#view_type> for #type_name #type_generics
impl #impl_generics gpui::RenderOnce for #type_name #type_generics
#where_clause
{
type Element = gpui::CompositeElement<#view_type, Self>;
type Element = gpui::CompositeElement<Self>;
fn element_id(&self) -> Option<ElementId> {
None
@@ -45,20 +25,3 @@ pub fn derive_render_once(input: TokenStream) -> TokenStream {
gen.into()
}
fn specified_view_type(ast: &DeriveInput) -> Option<proc_macro2::Ident> {
ast.attrs.iter().find_map(|attr| {
if attr.path.is_ident("view") {
if let Ok(syn::Meta::NameValue(meta_name_value)) = attr.parse_meta() {
if let syn::Lit::Str(lit_str) = meta_name_value.lit {
return Some(
lit_str
.parse::<syn::Ident>()
.expect("Failed to parse view_type"),
);
}
}
}
None
})
}
+41
View File
@@ -7,6 +7,7 @@ pub use crate::{
use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
language_settings::{language_settings, LanguageSettings},
markdown::parse_markdown,
outline::OutlineItem,
syntax_map::{
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
@@ -155,12 +156,52 @@ pub struct Diagnostic {
pub is_unnecessary: bool,
}
pub async fn prepare_completion_documentation(
documentation: &lsp::Documentation,
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
) -> Documentation {
match documentation {
lsp::Documentation::String(text) => {
if text.lines().count() <= 1 {
Documentation::SingleLine(text.clone())
} else {
Documentation::MultiLinePlainText(text.clone())
}
}
lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
lsp::MarkupKind::PlainText => {
if value.lines().count() <= 1 {
Documentation::SingleLine(value.clone())
} else {
Documentation::MultiLinePlainText(value.clone())
}
}
lsp::MarkupKind::Markdown => {
let parsed = parse_markdown(value, language_registry, language).await;
Documentation::MultiLineMarkdown(parsed)
}
},
}
}
#[derive(Clone, Debug)]
pub enum Documentation {
Undocumented,
SingleLine(String),
MultiLinePlainText(String),
MultiLineMarkdown(ParsedMarkdown),
}
#[derive(Clone, Debug)]
pub struct Completion {
pub old_range: Range<Anchor>,
pub new_text: String,
pub label: CodeLabel,
pub server_id: LanguageServerId,
pub documentation: Option<Documentation>,
pub lsp_completion: lsp::CompletionItem,
}
+1
View File
@@ -482,6 +482,7 @@ pub async fn deserialize_completion(
lsp_completion.filter_text.as_deref(),
)
}),
documentation: None,
server_id: LanguageServerId(completion.server_id as usize),
lsp_completion,
})
+44 -38
View File
@@ -1,10 +1,10 @@
use editor::Editor;
use gpui::{
div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton,
Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
};
use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Divider, Label, TextColor};
use ui::{prelude::*, v_stack, Color, Divider, Label};
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
@@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> {
}
pub trait PickerDelegate: Sized + 'static {
type ListItem: RenderOnce<Picker<Self>>;
type ListItem: RenderOnce;
fn match_count(&self) -> usize;
fn selected_index(&self) -> usize;
@@ -180,21 +180,21 @@ impl<D: PickerDelegate> Picker<D> {
}
}
impl<D: PickerDelegate> Render<Self> for Picker<D> {
type Element = Div<Self>;
impl<D: PickerDelegate> Render for Picker<D> {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.key_context("picker")
.size_full()
.elevation_2(cx)
.on_action(Self::select_next)
.on_action(Self::select_prev)
.on_action(Self::select_first)
.on_action(Self::select_last)
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(Self::secondary_confirm)
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::secondary_confirm))
.child(
v_stack()
.py_0p5()
@@ -208,31 +208,37 @@ impl<D: PickerDelegate> Render<Self> for Picker<D> {
.p_1()
.grow()
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| {
div()
.on_mouse_down(
MouseButton::Left,
move |this: &mut Self, event, cx| {
this.handle_click(
ix,
event.modifiers.command,
cx,
)
},
)
.child(this.delegate.render_match(
ix,
ix == selected_ix,
cx,
))
})
.collect()
}
})
uniform_list(
cx.view().clone(),
"candidates",
self.delegate.match_count(),
{
let selected_index = self.delegate.selected_index();
move |picker, visible_range, cx| {
visible_range
.map(|ix| {
div()
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, event: &MouseDownEvent, cx| {
this.handle_click(
ix,
event.modifiers.command,
cx,
)
}),
)
.child(picker.delegate.render_match(
ix,
ix == selected_index,
cx,
))
})
.collect()
}
},
)
.track_scroll(self.scroll_handle.clone()),
)
.max_h_72()
@@ -244,7 +250,7 @@ impl<D: PickerDelegate> Render<Self> for Picker<D> {
v_stack().p_1().grow().child(
div()
.px_1()
.child(Label::new("No matches").color(TextColor::Muted)),
.child(Label::new("No matches").color(Color::Muted)),
),
)
})
+21 -4
View File
@@ -10,7 +10,7 @@ use futures::future;
use gpui::{AppContext, AsyncAppContext, Model};
use language::{
language_settings::{language_settings, InlayHintKind},
point_from_lsp, point_to_lsp,
point_from_lsp, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
@@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions {
async fn response_from_lsp(
self,
completions: Option<lsp::CompletionResponse>,
_: Model<Project>,
project: Model<Project>,
buffer: Model<Buffer>,
server_id: LanguageServerId,
mut cx: AsyncAppContext,
@@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions {
Default::default()
};
let completions = buffer.update(&mut cx, |buffer, _| {
let completions = buffer.update(&mut cx, |buffer, cx| {
let language_registry = project.read(cx).languages().clone();
let language = buffer.language().cloned();
let snapshot = buffer.snapshot();
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
@@ -1443,14 +1444,29 @@ impl LspCommand for GetCompletions {
}
};
let language_registry = language_registry.clone();
let language = language.clone();
LineEnding::normalize(&mut new_text);
Some(async move {
let mut label = None;
if let Some(language) = language {
if let Some(language) = language.as_ref() {
language.process_completion(&mut lsp_completion).await;
label = language.label_for_completion(&lsp_completion).await;
}
let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
Some(
prepare_completion_documentation(
lsp_docs,
&language_registry,
language.clone(),
)
.await,
)
} else {
None
};
Completion {
old_range,
new_text,
@@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions {
lsp_completion.filter_text.as_deref(),
)
}),
documentation,
server_id,
lsp_completion,
}
+46 -40
View File
@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
use gpui::{
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, Stateful,
StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext,
VisualContext as _, WeakView, WindowContext,
Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render,
RenderOnce, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View,
ViewContext, VisualContext as _, WeakView, WindowContext,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{
@@ -371,7 +371,7 @@ impl ProjectPanel {
_entry_id: ProjectEntryId,
_cx: &mut ViewContext<Self>,
) {
todo!()
// todo!()
// let project = self.project.read(cx);
// let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
@@ -1339,7 +1339,7 @@ impl ProjectPanel {
editor: Option<&View<Editor>>,
padding: Pixels,
cx: &mut ViewContext<Self>,
) -> Div<Self> {
) -> Div {
let show_editor = details.is_editing && !details.is_processing;
let theme = cx.theme();
@@ -1378,7 +1378,7 @@ impl ProjectPanel {
details: EntryDetails,
// dragged_entry_destination: &mut Option<Arc<Path>>,
cx: &mut ViewContext<Self>,
) -> Stateful<Self, Div<Self>> {
) -> Stateful<Div> {
let kind = details.kind;
let settings = ProjectPanelSettings::get_global(cx);
const INDENT_SIZE: Pixels = px(16.0);
@@ -1396,7 +1396,7 @@ impl ProjectPanel {
this.bg(cx.theme().colors().element_selected)
})
.hover(|style| style.bg(cx.theme().colors().element_hover))
.on_click(move |this, event, cx| {
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
if !show_editor {
if kind.is_dir() {
this.toggle_expanded(entry_id, cx);
@@ -1408,10 +1408,13 @@ impl ProjectPanel {
}
}
}
})
.on_mouse_down(MouseButton::Right, move |this, event, cx| {
this.deploy_context_menu(event.position, entry_id, cx);
})
}))
.on_mouse_down(
MouseButton::Right,
cx.listener(move |this, event: &MouseDownEvent, cx| {
this.deploy_context_menu(event.position, entry_id, cx);
}),
)
// .on_drop::<ProjectEntryId>(|this, event, cx| {
// this.move_entry(
// *dragged_entry,
@@ -1423,10 +1426,10 @@ impl ProjectPanel {
}
}
impl Render<Self> for ProjectPanel {
type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
impl Render for ProjectPanel {
type Element = Focusable<Stateful<Div>>;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let has_worktree = self.visible_entries.len() != 0;
if has_worktree {
@@ -1434,40 +1437,43 @@ impl Render<Self> for ProjectPanel {
.id("project-panel")
.size_full()
.key_context("ProjectPanel")
.on_action(Self::select_next)
.on_action(Self::select_prev)
.on_action(Self::expand_selected_entry)
.on_action(Self::collapse_selected_entry)
.on_action(Self::collapse_all_entries)
.on_action(Self::new_file)
.on_action(Self::new_directory)
.on_action(Self::rename)
.on_action(Self::delete)
.on_action(Self::confirm)
.on_action(Self::open_file)
.on_action(Self::cancel)
.on_action(Self::cut)
.on_action(Self::copy)
.on_action(Self::copy_path)
.on_action(Self::copy_relative_path)
.on_action(Self::paste)
.on_action(Self::reveal_in_finder)
.on_action(Self::open_in_terminal)
.on_action(Self::new_search_in_directory)
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::expand_selected_entry))
.on_action(cx.listener(Self::collapse_selected_entry))
.on_action(cx.listener(Self::collapse_all_entries))
.on_action(cx.listener(Self::new_file))
.on_action(cx.listener(Self::new_directory))
.on_action(cx.listener(Self::rename))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::open_file))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::cut))
.on_action(cx.listener(Self::copy))
.on_action(cx.listener(Self::copy_path))
.on_action(cx.listener(Self::copy_relative_path))
.on_action(cx.listener(Self::paste))
.on_action(cx.listener(Self::reveal_in_finder))
.on_action(cx.listener(Self::open_in_terminal))
.on_action(cx.listener(Self::new_search_in_directory))
.track_focus(&self.focus_handle)
.child(
uniform_list(
cx.view().clone(),
"entries",
self.visible_entries
.iter()
.map(|(_, worktree_entries)| worktree_entries.len())
.sum(),
|this: &mut Self, range, cx| {
let mut items = Vec::new();
this.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(this.render_entry(id, details, cx));
});
items
{
|this, range, cx| {
let mut items = Vec::new();
this.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(this.render_entry(id, details, cx));
});
items
}
},
)
.size_full()
+2 -2
View File
@@ -56,12 +56,12 @@ pub struct Mention {
}
impl RichText {
pub fn element<V: 'static>(
pub fn element(
&self,
// syntax: Arc<SyntaxTheme>,
// style: RichTextStyle,
// cx: &mut ViewContext<V>,
) -> AnyElement<V> {
) -> AnyElement {
todo!();
// let mut region_id = 0;
+40
View File
@@ -0,0 +1,40 @@
[package]
name = "search2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/search.rs"
doctest = false
[dependencies]
bitflags = "1"
collections = { path = "../collections" }
editor = { package = "editor2", path = "../editor2" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
menu = { package = "menu2", path = "../menu2" }
project = { package = "project2", path = "../project2" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }
ui = {package = "ui2", path = "../ui2"}
workspace = { package = "workspace2", path = "../workspace2" }
#semantic_index = { path = "../semantic_index" }
anyhow.workspace = true
futures.workspace = true
log.workspace = true
postage.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
serde_json.workspace = true
[dev-dependencies]
client = { package = "client2", path = "../client2", features = ["test-support"] }
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
unindent.workspace = true
File diff suppressed because it is too large Load Diff
+184
View File
@@ -0,0 +1,184 @@
use smallvec::SmallVec;
const SEARCH_HISTORY_LIMIT: usize = 20;
#[derive(Default, Debug, Clone)]
pub struct SearchHistory {
history: SmallVec<[String; SEARCH_HISTORY_LIMIT]>,
selected: Option<usize>,
}
impl SearchHistory {
pub fn add(&mut self, search_string: String) {
if let Some(i) = self.selected {
if search_string == self.history[i] {
return;
}
}
if let Some(previously_searched) = self.history.last_mut() {
if search_string.find(previously_searched.as_str()).is_some() {
*previously_searched = search_string;
self.selected = Some(self.history.len() - 1);
return;
}
}
self.history.push(search_string);
if self.history.len() > SEARCH_HISTORY_LIMIT {
self.history.remove(0);
}
self.selected = Some(self.history.len() - 1);
}
pub fn next(&mut self) -> Option<&str> {
let history_size = self.history.len();
if history_size == 0 {
return None;
}
let selected = self.selected?;
if selected == history_size - 1 {
return None;
}
let next_index = selected + 1;
self.selected = Some(next_index);
Some(&self.history[next_index])
}
pub fn current(&self) -> Option<&str> {
Some(&self.history[self.selected?])
}
pub fn previous(&mut self) -> Option<&str> {
let history_size = self.history.len();
if history_size == 0 {
return None;
}
let prev_index = match self.selected {
Some(selected_index) => {
if selected_index == 0 {
return None;
} else {
selected_index - 1
}
}
None => history_size - 1,
};
self.selected = Some(prev_index);
Some(&self.history[prev_index])
}
pub fn reset_selection(&mut self) {
self.selected = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
let mut search_history = SearchHistory::default();
assert_eq!(
search_history.current(),
None,
"No current selection should be set fo the default search history"
);
search_history.add("rust".to_string());
assert_eq!(
search_history.current(),
Some("rust"),
"Newly added item should be selected"
);
// check if duplicates are not added
search_history.add("rust".to_string());
assert_eq!(
search_history.history.len(),
1,
"Should not add a duplicate"
);
assert_eq!(search_history.current(), Some("rust"));
// check if new string containing the previous string replaces it
search_history.add("rustlang".to_string());
assert_eq!(
search_history.history.len(),
1,
"Should replace previous item if it's a substring"
);
assert_eq!(search_history.current(), Some("rustlang"));
// push enough items to test SEARCH_HISTORY_LIMIT
for i in 0..SEARCH_HISTORY_LIMIT * 2 {
search_history.add(format!("item{i}"));
}
assert!(search_history.history.len() <= SEARCH_HISTORY_LIMIT);
}
#[test]
fn test_next_and_previous() {
let mut search_history = SearchHistory::default();
assert_eq!(
search_history.next(),
None,
"Default search history should not have a next item"
);
search_history.add("Rust".to_string());
assert_eq!(search_history.next(), None);
search_history.add("JavaScript".to_string());
assert_eq!(search_history.next(), None);
search_history.add("TypeScript".to_string());
assert_eq!(search_history.next(), None);
assert_eq!(search_history.current(), Some("TypeScript"));
assert_eq!(search_history.previous(), Some("JavaScript"));
assert_eq!(search_history.current(), Some("JavaScript"));
assert_eq!(search_history.previous(), Some("Rust"));
assert_eq!(search_history.current(), Some("Rust"));
assert_eq!(search_history.previous(), None);
assert_eq!(search_history.current(), Some("Rust"));
assert_eq!(search_history.next(), Some("JavaScript"));
assert_eq!(search_history.current(), Some("JavaScript"));
assert_eq!(search_history.next(), Some("TypeScript"));
assert_eq!(search_history.current(), Some("TypeScript"));
assert_eq!(search_history.next(), None);
assert_eq!(search_history.current(), Some("TypeScript"));
}
#[test]
fn test_reset_selection() {
let mut search_history = SearchHistory::default();
search_history.add("Rust".to_string());
search_history.add("JavaScript".to_string());
search_history.add("TypeScript".to_string());
assert_eq!(search_history.current(), Some("TypeScript"));
search_history.reset_selection();
assert_eq!(search_history.current(), None);
assert_eq!(
search_history.previous(),
Some("TypeScript"),
"Should start from the end after reset on previous item query"
);
search_history.previous();
assert_eq!(search_history.current(), Some("JavaScript"));
search_history.previous();
assert_eq!(search_history.current(), Some("Rust"));
search_history.reset_selection();
assert_eq!(search_history.current(), None);
}
}
+32
View File
@@ -0,0 +1,32 @@
// TODO: Update the default search mode to get from config
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub enum SearchMode {
#[default]
Text,
Semantic,
Regex,
}
impl SearchMode {
pub(crate) fn label(&self) -> &'static str {
match self {
SearchMode::Text => "Text",
SearchMode::Semantic => "Semantic",
SearchMode::Regex => "Regex",
}
}
}
pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
match mode {
SearchMode::Text => SearchMode::Regex,
SearchMode::Regex => {
if semantic_enabled {
SearchMode::Semantic
} else {
SearchMode::Text
}
}
SearchMode::Semantic => SearchMode::Text,
}
}
File diff suppressed because it is too large Load Diff
+117
View File
@@ -0,0 +1,117 @@
use bitflags::bitflags;
pub use buffer_search::BufferSearchBar;
use gpui::{actions, Action, AppContext, RenderOnce};
pub use mode::SearchMode;
use project::search::SearchQuery;
use ui::ButtonVariant;
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
// use theme::components::{
// action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
// };
pub mod buffer_search;
mod history;
mod mode;
//pub mod project_search;
pub(crate) mod search_bar;
pub fn init(cx: &mut AppContext) {
buffer_search::init(cx);
//project_search::init(cx);
}
actions!(
CycleMode,
ToggleWholeWord,
ToggleCaseSensitive,
ToggleReplace,
SelectNextMatch,
SelectPrevMatch,
SelectAllMatches,
NextHistoryQuery,
PreviousHistoryQuery,
ActivateTextMode,
ActivateSemanticMode,
ActivateRegexMode,
ReplaceAll,
ReplaceNext,
);
bitflags! {
#[derive(Default)]
pub struct SearchOptions: u8 {
const NONE = 0b000;
const WHOLE_WORD = 0b001;
const CASE_SENSITIVE = 0b010;
}
}
impl SearchOptions {
pub fn label(&self) -> &'static str {
match *self {
SearchOptions::WHOLE_WORD => "Match Whole Word",
SearchOptions::CASE_SENSITIVE => "Match Case",
_ => panic!("{:?} is not a named SearchOption", self),
}
}
pub fn icon(&self) -> ui::Icon {
match *self {
SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
_ => panic!("{:?} is not a named SearchOption", self),
}
}
pub fn to_toggle_action(&self) -> Box<dyn Action + Sync + Send + 'static> {
match *self {
SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
_ => panic!("{:?} is not a named SearchOption", self),
}
}
pub fn none() -> SearchOptions {
SearchOptions::NONE
}
pub fn from_query(query: &SearchQuery) -> SearchOptions {
let mut options = SearchOptions::NONE;
options.set(SearchOptions::WHOLE_WORD, query.whole_word());
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
options
}
pub fn as_button(&self, active: bool) -> impl RenderOnce {
ui::IconButton::new(0, self.icon())
.on_click({
let action = self.to_toggle_action();
move |_, cx| {
cx.dispatch_action(action.boxed_clone());
}
})
.variant(ui::ButtonVariant::Ghost)
.when(active, |button| button.variant(ButtonVariant::Filled))
}
}
fn toggle_replace_button(active: bool) -> impl RenderOnce {
// todo: add toggle_replace button
ui::IconButton::new(0, ui::Icon::Replace)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(ToggleReplace));
cx.notify();
})
.variant(ui::ButtonVariant::Ghost)
.when(active, |button| button.variant(ButtonVariant::Filled))
}
fn render_replace_button(
action: impl Action + 'static + Send + Sync,
icon: ui::Icon,
) -> impl RenderOnce {
// todo: add tooltip
ui::IconButton::new(0, icon).on_click(move |_, cx| {
cx.dispatch_action(action.boxed_clone());
})
}
+35
View File
@@ -0,0 +1,35 @@
use gpui::{MouseDownEvent, RenderOnce, WindowContext};
use ui::{Button, ButtonVariant, IconButton};
use crate::mode::SearchMode;
pub(super) fn render_nav_button(
icon: ui::Icon,
_active: bool,
on_click: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) -> impl RenderOnce {
// let tooltip_style = cx.theme().tooltip.clone();
// let cursor_style = if active {
// CursorStyle::PointingHand
// } else {
// CursorStyle::default()
// };
// enum NavButton {}
IconButton::new("search-nav-button", icon).on_click(on_click)
}
pub(crate) fn render_search_mode_button(
mode: SearchMode,
is_active: bool,
on_click: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) -> Button {
let button_variant = if is_active {
ButtonVariant::Filled
} else {
ButtonVariant::Ghost
};
Button::new(mode.label())
.on_click(on_click)
.variant(button_variant)
}
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "story"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gpui = { package = "gpui2", path = "../gpui2" }
+3
View File
@@ -0,0 +1,3 @@
mod story;
pub use story::*;
+35
View File
@@ -0,0 +1,35 @@
use gpui::prelude::*;
use gpui::{div, hsla, Div, SharedString};
pub struct Story {}
impl Story {
pub fn container() -> Div {
div().size_full().flex().flex_col().pt_2().px_4().bg(hsla(
0. / 360.,
0. / 100.,
100. / 100.,
1.,
))
}
pub fn title(title: impl Into<SharedString>) -> impl Element {
div()
.text_xl()
.text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
.child(title.into())
}
pub fn title_for<T>() -> impl Element {
Self::title(std::any::type_name::<T>())
}
pub fn label(label: impl Into<SharedString>) -> impl Element {
div()
.mt_4()
.mb_2()
.text_xs()
.text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
.child(label.into())
}
}
+1
View File
@@ -25,6 +25,7 @@ serde.workspace = true
settings2 = { path = "../settings2" }
simplelog = "0.9"
smallvec.workspace = true
story = { path = "../story" }
strum = { version = "0.25.0", features = ["derive"] }
theme = { path = "../theme" }
theme2 = { path = "../theme2" }
-2
View File
@@ -1,4 +1,3 @@
mod colors;
mod focus;
mod kitchen_sink;
mod picker;
@@ -6,7 +5,6 @@ mod scroll;
mod text;
mod z_index;
pub use colors::*;
pub use focus::*;
pub use kitchen_sink::*;
pub use picker::*;
-44
View File
@@ -1,44 +0,0 @@
use crate::story::Story;
use gpui::{prelude::*, px, Div, Render};
use theme2::{default_color_scales, ColorScaleStep};
use ui::prelude::*;
pub struct ColorsStory;
impl Render<Self> for ColorsStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let color_scales = default_color_scales();
Story::container(cx)
.child(Story::title(cx, "Colors"))
.child(
div()
.id("colors")
.flex()
.flex_col()
.gap_1()
.overflow_y_scroll()
.text_color(gpui::white())
.children(color_scales.into_iter().map(|scale| {
div()
.flex()
.child(
div()
.w(px(75.))
.line_height(px(24.))
.child(scale.name().clone()),
)
.child(
div()
.flex()
.gap_1()
.children(ColorScaleStep::ALL.map(|step| {
div().flex().size_6().bg(scale.step(cx, step))
})),
)
})),
)
}
}
+32 -28
View File
@@ -26,8 +26,8 @@ impl FocusStory {
}
}
impl Render<Self> for FocusStory {
type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
impl Render for FocusStory {
type Element = Focusable<Stateful<Div>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
@@ -42,18 +42,18 @@ impl Render<Self> for FocusStory {
.id("parent")
.focusable()
.key_context("parent")
.on_action(|_, action: &ActionA, cx| {
.on_action(cx.listener(|_, action: &ActionA, cx| {
println!("Action A dispatched on parent");
})
.on_action(|_, action: &ActionB, cx| {
}))
.on_action(cx.listener(|_, action: &ActionB, cx| {
println!("Action B dispatched on parent");
})
.on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred"))
.on_focus_in(|_, _, _| println!("Parent focus_in"))
.on_focus_out(|_, _, _| println!("Parent focus_out"))
.on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event))
.on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event))
}))
.on_focus(cx.listener(|_, _, _| println!("Parent focused")))
.on_blur(cx.listener(|_, _, _| println!("Parent blurred")))
.on_focus_in(cx.listener(|_, _, _| println!("Parent focus_in")))
.on_focus_out(cx.listener(|_, _, _| println!("Parent focus_out")))
.on_key_down(cx.listener(|_, event, _| println!("Key down on parent {:?}", event)))
.on_key_up(cx.listener(|_, event, _| println!("Key up on parent {:?}", event)))
.size_full()
.bg(color_1)
.focus(|style| style.bg(color_2))
@@ -61,38 +61,42 @@ impl Render<Self> for FocusStory {
div()
.track_focus(&self.child_1_focus)
.key_context("child-1")
.on_action(|_, action: &ActionB, cx| {
.on_action(cx.listener(|_, action: &ActionB, cx| {
println!("Action B dispatched on child 1 during");
})
}))
.w_full()
.h_6()
.bg(color_4)
.focus(|style| style.bg(color_5))
.in_focus(|style| style.bg(color_6))
.on_focus(|_, _, _| println!("Child 1 focused"))
.on_blur(|_, _, _| println!("Child 1 blurred"))
.on_focus_in(|_, _, _| println!("Child 1 focus_in"))
.on_focus_out(|_, _, _| println!("Child 1 focus_out"))
.on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event))
.on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event))
.on_focus(cx.listener(|_, _, _| println!("Child 1 focused")))
.on_blur(cx.listener(|_, _, _| println!("Child 1 blurred")))
.on_focus_in(cx.listener(|_, _, _| println!("Child 1 focus_in")))
.on_focus_out(cx.listener(|_, _, _| println!("Child 1 focus_out")))
.on_key_down(
cx.listener(|_, event, _| println!("Key down on child 1 {:?}", event)),
)
.on_key_up(cx.listener(|_, event, _| println!("Key up on child 1 {:?}", event)))
.child("Child 1"),
)
.child(
div()
.track_focus(&self.child_2_focus)
.key_context("child-2")
.on_action(|_, action: &ActionC, cx| {
.on_action(cx.listener(|_, action: &ActionC, cx| {
println!("Action C dispatched on child 2");
})
}))
.w_full()
.h_6()
.bg(color_4)
.on_focus(|_, _, _| println!("Child 2 focused"))
.on_blur(|_, _, _| println!("Child 2 blurred"))
.on_focus_in(|_, _, _| println!("Child 2 focus_in"))
.on_focus_out(|_, _, _| println!("Child 2 focus_out"))
.on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event))
.on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event))
.on_focus(cx.listener(|_, _, _| println!("Child 2 focused")))
.on_blur(cx.listener(|_, _, _| println!("Child 2 blurred")))
.on_focus_in(cx.listener(|_, _, _| println!("Child 2 focus_in")))
.on_focus_out(cx.listener(|_, _, _| println!("Child 2 focus_out")))
.on_key_down(
cx.listener(|_, event, _| println!("Key down on child 2 {:?}", event)),
)
.on_key_up(cx.listener(|_, event, _| println!("Key up on child 2 {:?}", event)))
.child("Child 2"),
)
}
@@ -1,8 +1,10 @@
use crate::{story::Story, story_selector::ComponentStory};
use gpui::{prelude::*, Div, Render, Stateful, View};
use story::Story;
use strum::IntoEnumIterator;
use ui::prelude::*;
use crate::story_selector::ComponentStory;
pub struct KitchenSinkStory;
impl KitchenSinkStory {
@@ -11,19 +13,19 @@ impl KitchenSinkStory {
}
}
impl Render<Self> for KitchenSinkStory {
type Element = Stateful<Self, Div<Self>>;
impl Render for KitchenSinkStory {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let component_stories = ComponentStory::iter()
.map(|selector| selector.story(cx))
.collect::<Vec<_>>();
Story::container(cx)
Story::container()
.id("kitchen-sink")
.overflow_y_scroll()
.child(Story::title(cx, "Kitchen Sink"))
.child(Story::label(cx, "Components"))
.child(Story::title("Kitchen Sink"))
.child(Story::label("Components"))
.child(div().flex().flex_col().children(component_stories))
// Add a bit of space at the bottom of the kitchen sink so elements
// don't end up squished right up against the bottom of the screen.
+3 -3
View File
@@ -36,7 +36,7 @@ impl Delegate {
}
impl PickerDelegate for Delegate {
type ListItem = Div<Picker<Self>>;
type ListItem = Div;
fn match_count(&self) -> usize {
self.candidates.len()
@@ -205,8 +205,8 @@ impl PickerStory {
}
}
impl Render<Self> for PickerStory {
type Element = Div<Self>;
impl Render for PickerStory {
type Element = Div;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div()
+3 -3
View File
@@ -10,8 +10,8 @@ impl ScrollStory {
}
}
impl Render<Self> for ScrollStory {
type Element = Stateful<Self, Div<Self>>;
impl Render for ScrollStory {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
@@ -38,7 +38,7 @@ impl Render<Self> for ScrollStory {
};
div()
.id(id)
.tooltip(move |_, cx| Tooltip::text(format!("{}, {}", row, column), cx))
.tooltip(move |cx| Tooltip::text(format!("{}, {}", row, column), cx))
.bg(bg)
.size(px(100. as f32))
.when(row >= 5 && column >= 5, |d| {
+2 -2
View File
@@ -11,8 +11,8 @@ impl TextStory {
}
}
impl Render<Self> for TextStory {
type Element = Div<Self>;
impl Render for TextStory {
type Element = Div;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
v_stack()
+41 -44
View File
@@ -1,52 +1,49 @@
use gpui::{px, rgb, Div, Hsla, Render, RenderOnce};
use story::Story;
use ui::prelude::*;
use crate::story::Story;
/// A reimplementation of the MDN `z-index` example, found here:
/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
pub struct ZIndexStory;
impl Render<Self> for ZIndexStory {
type Element = Div<Self>;
impl Render for ZIndexStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title(cx, "z-index"))
.child(
div()
.flex()
.child(
div()
.w(px(250.))
.child(Story::label(cx, "z-index: auto"))
.child(ZIndexExample::new(0)),
)
.child(
div()
.w(px(250.))
.child(Story::label(cx, "z-index: 1"))
.child(ZIndexExample::new(1)),
)
.child(
div()
.w(px(250.))
.child(Story::label(cx, "z-index: 3"))
.child(ZIndexExample::new(3)),
)
.child(
div()
.w(px(250.))
.child(Story::label(cx, "z-index: 5"))
.child(ZIndexExample::new(5)),
)
.child(
div()
.w(px(250.))
.child(Story::label(cx, "z-index: 7"))
.child(ZIndexExample::new(7)),
),
)
Story::container().child(Story::title("z-index")).child(
div()
.flex()
.child(
div()
.w(px(250.))
.child(Story::label("z-index: auto"))
.child(ZIndexExample::new(0)),
)
.child(
div()
.w(px(250.))
.child(Story::label("z-index: 1"))
.child(ZIndexExample::new(1)),
)
.child(
div()
.w(px(250.))
.child(Story::label("z-index: 3"))
.child(ZIndexExample::new(3)),
)
.child(
div()
.w(px(250.))
.child(Story::label("z-index: 5"))
.child(ZIndexExample::new(5)),
)
.child(
div()
.w(px(250.))
.child(Story::label("z-index: 7"))
.child(ZIndexExample::new(7)),
),
)
}
}
@@ -77,17 +74,17 @@ trait Styles: Styled + Sized {
}
}
impl<V: 'static> Styles for Div<V> {}
impl Styles for Div {}
#[derive(RenderOnce)]
struct ZIndexExample {
z_index: u32,
}
impl<V: 'static> Component<V> for ZIndexExample {
type Rendered = Div<V>;
impl Component for ZIndexExample {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.relative()
.size_full()
-1
View File
@@ -1 +0,0 @@
pub use ui::Story;
+1 -55
View File
@@ -8,49 +8,22 @@ use clap::ValueEnum;
use gpui::{AnyView, VisualContext};
use strum::{EnumIter, EnumString, IntoEnumIterator};
use ui::prelude::*;
use ui::{AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory};
use ui::{AvatarStory, ButtonStory, IconStory, InputStory, LabelStory};
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
#[strum(serialize_all = "snake_case")]
pub enum ComponentStory {
AssistantPanel,
Avatar,
Breadcrumb,
Buffer,
Button,
ChatPanel,
Checkbox,
CollabPanel,
Colors,
CommandPalette,
ContextMenu,
Copilot,
Details,
Facepile,
Focus,
Icon,
Input,
Keybinding,
Label,
LanguageSelector,
MultiBuffer,
NotificationsPanel,
Palette,
Panel,
ProjectPanel,
Players,
RecentProjects,
Scroll,
Tab,
TabBar,
Terminal,
Text,
ThemeSelector,
TitleBar,
Toast,
Toolbar,
TrafficLights,
Workspace,
ZIndex,
Picker,
}
@@ -58,44 +31,17 @@ pub enum ComponentStory {
impl ComponentStory {
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
match self {
Self::AssistantPanel => cx.build_view(|_| ui::AssistantPanelStory).into(),
Self::Avatar => cx.build_view(|_| AvatarStory).into(),
Self::Breadcrumb => cx.build_view(|_| ui::BreadcrumbStory).into(),
Self::Buffer => cx.build_view(|_| ui::BufferStory).into(),
Self::Button => cx.build_view(|_| ButtonStory).into(),
Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into(),
Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into(),
Self::Colors => cx.build_view(|_| ColorsStory).into(),
Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into(),
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
Self::Copilot => cx.build_view(|_| ui::CopilotModalStory).into(),
Self::Details => cx.build_view(|_| DetailsStory).into(),
Self::Facepile => cx.build_view(|_| ui::FacepileStory).into(),
Self::Focus => FocusStory::view(cx).into(),
Self::Icon => cx.build_view(|_| IconStory).into(),
Self::Input => cx.build_view(|_| InputStory).into(),
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
Self::Label => cx.build_view(|_| LabelStory).into(),
Self::LanguageSelector => cx.build_view(|_| ui::LanguageSelectorStory).into(),
Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(),
Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(),
Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(),
Self::Players => cx.build_view(|_| theme2::PlayerStory).into(),
Self::Panel => cx.build_view(|cx| ui::PanelStory).into(),
Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(),
Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(),
Self::Scroll => ScrollStory::view(cx).into(),
Self::Tab => cx.build_view(|_| ui::TabStory).into(),
Self::TabBar => cx.build_view(|_| ui::TabBarStory).into(),
Self::Terminal => cx.build_view(|_| ui::TerminalStory).into(),
Self::Text => TextStory::view(cx).into(),
Self::ThemeSelector => cx.build_view(|_| ui::ThemeSelectorStory).into(),
Self::TitleBar => ui::TitleBarStory::view(cx).into(),
Self::Toast => cx.build_view(|_| ui::ToastStory).into(),
Self::Toolbar => cx.build_view(|_| ui::ToolbarStory).into(),
Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
Self::Workspace => ui::WorkspaceStory::view(cx).into(),
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
Self::Picker => PickerStory::new(cx).into(),
}
+3 -7
View File
@@ -2,7 +2,6 @@
mod assets;
mod stories;
mod story;
mod story_selector;
use std::sync::Arc;
@@ -15,7 +14,6 @@ use gpui::{
use log::LevelFilter;
use settings2::{default_settings, Settings, SettingsStore};
use simplelog::SimpleLogger;
use story_selector::ComponentStory;
use theme2::{ThemeRegistry, ThemeSettings};
use ui::prelude::*;
@@ -62,15 +60,13 @@ fn main() {
theme2::init(theme2::LoadThemes::All, cx);
let selector =
story_selector.unwrap_or(StorySelector::Component(ComponentStory::Workspace));
let selector = story_selector.unwrap_or(StorySelector::KitchenSink);
let theme_registry = cx.global::<ThemeRegistry>();
let mut theme_settings = ThemeSettings::get_global(cx).clone();
theme_settings.active_theme = theme_registry.get(&theme_name).unwrap();
ThemeSettings::override_global(theme_settings, cx);
ui::settings::init(cx);
language::init(cx);
editor::init(cx);
@@ -105,8 +101,8 @@ impl StoryWrapper {
}
}
impl Render<Self> for StoryWrapper {
type Element = Div<Self>;
impl Render for StoryWrapper {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
-17
View File
@@ -1,17 +0,0 @@
[package]
name = "storybook3"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "storybook"
path = "src/storybook3.rs"
[dependencies]
anyhow.workspace = true
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2", features = ["stories"] }
theme = { package = "theme2", path = "../theme2", features = ["stories"] }
settings = { package = "settings2", path = "../settings2"}
-87
View File
@@ -1,87 +0,0 @@
use anyhow::Result;
use gpui::{
div, px, size, AnyView, Bounds, Div, Render, ViewContext, VisualContext, WindowBounds,
WindowOptions,
};
use gpui::{white, AssetSource};
use settings::{default_settings, Settings, SettingsStore};
use std::borrow::Cow;
use std::sync::Arc;
use theme::ThemeSettings;
use ui::{prelude::*, ContextMenuStory};
struct Assets;
impl AssetSource for Assets {
fn load(&self, _path: &str) -> Result<Cow<[u8]>> {
todo!();
}
fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
Ok(vec![])
}
}
fn main() {
let asset_source = Arc::new(Assets);
gpui::App::production(asset_source).run(move |cx| {
let mut store = SettingsStore::default();
store
.set_default_settings(default_settings().as_ref(), cx)
.unwrap();
cx.set_global(store);
ui::settings::init(cx);
theme::init(theme::LoadThemes::JustBase, cx);
cx.open_window(
WindowOptions {
bounds: WindowBounds::Fixed(Bounds {
origin: Default::default(),
size: size(px(1500.), px(780.)).into(),
}),
..Default::default()
},
move |cx| {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
cx.set_rem_size(ui_font_size);
cx.build_view(|cx| TestView {
story: cx.build_view(|_| ContextMenuStory).into(),
})
},
);
cx.activate(true);
})
}
struct TestView {
#[allow(unused)]
story: AnyView,
}
impl Render<Self> for TestView {
type Element = Div<Self>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div()
.flex()
.bg(gpui::blue())
.flex_col()
.size_full()
.font("Helvetica")
.child(div().h_5())
.child(
div()
.flex()
.w_96()
.bg(white())
.relative()
.child(div().child(concat!(
"The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
))),
)
}
}
+2 -2
View File
@@ -335,8 +335,8 @@ impl TerminalPanel {
impl EventEmitter<PanelEvent> for TerminalPanel {}
impl Render<Self> for TerminalPanel {
type Element = Div<Self>;
impl Render for TerminalPanel {
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div().child(self.pane.clone())
+30 -43
View File
@@ -9,10 +9,10 @@ pub mod terminal_panel;
// use crate::terminal_element::TerminalElement;
use editor::{scroll::autoscroll::Autoscroll, Editor};
use gpui::{
actions, div, Action, AnyElement, AppContext, DispatchPhase, Div, Element, EventEmitter,
FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, InputHandler,
InteractiveElement, KeyDownEvent, Keystroke, Model, MouseButton, ParentElement, Pixels, Render,
SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView,
actions, div, Action, AnyElement, AppContext, Div, Element, EventEmitter, FocusEvent,
FocusHandle, Focusable, FocusableElement, FocusableView, InputHandler, InteractiveElement,
KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Render,
SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use language::Bias;
use persistence::TERMINAL_DB;
@@ -31,7 +31,7 @@ use workspace::{
notifications::NotifyResultExt,
register_deserializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem},
ui::{ContextMenu, Icon, IconElement, Label, ListEntry},
ui::{ContextMenu, Icon, IconElement, Label},
CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
};
@@ -63,7 +63,6 @@ pub struct SendKeystroke(String);
actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
pub fn init(cx: &mut AppContext) {
workspace::ui::init(cx);
terminal_panel::init(cx);
terminal::init(cx);
@@ -84,7 +83,7 @@ pub struct TerminalView {
has_new_content: bool,
//Currently using iTerm bell, show bell emoji in tab until input is received
has_bell: bool,
context_menu: Option<View<ContextMenu<Self>>>,
context_menu: Option<View<ContextMenu>>,
blink_state: bool,
blinking_on: bool,
blinking_paused: bool,
@@ -300,14 +299,8 @@ impl TerminalView {
cx: &mut ViewContext<Self>,
) {
self.context_menu = Some(ContextMenu::build(cx, |menu, _| {
menu.action(
ListEntry::new("clear", Label::new("Clear")),
Box::new(Clear),
)
.action(
ListEntry::new("close", Label::new("Close")),
Box::new(CloseActiveItem { save_intent: None }),
)
menu.action("Clear", Box::new(Clear))
.action("Close", Box::new(CloseActiveItem { save_intent: None }))
}));
dbg!(&position);
// todo!()
@@ -508,12 +501,7 @@ pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<Re
}
impl TerminalView {
fn key_down(
&mut self,
event: &KeyDownEvent,
_dispatch_phase: DispatchPhase,
cx: &mut ViewContext<Self>,
) {
fn key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) {
self.clear_bel(cx);
self.pause_cursor_blinking(cx);
@@ -540,8 +528,8 @@ impl TerminalView {
}
}
impl Render<Self> for TerminalView {
type Element = Focusable<Self, Div<Self>>;
impl Render for TerminalView {
type Element = Focusable<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let terminal_handle = self.terminal.clone().downgrade();
@@ -555,14 +543,14 @@ impl Render<Self> for TerminalView {
div()
.z_index(0)
.absolute()
.on_key_down(Self::key_down)
.on_action(TerminalView::send_text)
.on_action(TerminalView::send_keystroke)
.on_action(TerminalView::copy)
.on_action(TerminalView::paste)
.on_action(TerminalView::clear)
.on_action(TerminalView::show_character_palette)
.on_action(TerminalView::select_all)
.on_key_down(cx.listener(Self::key_down))
.on_action(cx.listener(TerminalView::send_text))
.on_action(cx.listener(TerminalView::send_keystroke))
.on_action(cx.listener(TerminalView::copy))
.on_action(cx.listener(TerminalView::paste))
.on_action(cx.listener(TerminalView::clear))
.on_action(cx.listener(TerminalView::show_character_palette))
.on_action(cx.listener(TerminalView::select_all))
// todo!()
.child(
"TERMINAL HERE", // TerminalElement::new(
@@ -572,10 +560,13 @@ impl Render<Self> for TerminalView {
// self.can_navigate_to_selected_word,
// )
)
.on_mouse_down(MouseButton::Right, |this, event, cx| {
this.deploy_context_menu(event.position, cx);
cx.notify();
}),
.on_mouse_down(
MouseButton::Right,
cx.listener(|this, event: &MouseDownEvent, cx| {
this.deploy_context_menu(event.position, cx);
cx.notify();
}),
),
)
.children(
self.context_menu
@@ -583,8 +574,8 @@ impl Render<Self> for TerminalView {
.map(|context_menu| div().z_index(1).absolute().child(context_menu)),
)
.track_focus(&self.focus_handle)
.on_focus_in(Self::focus_in)
.on_focus_out(Self::focus_out)
.on_focus_in(cx.listener(Self::focus_in))
.on_focus_out(cx.listener(Self::focus_out))
}
}
@@ -749,11 +740,7 @@ impl Item for TerminalView {
Some(self.terminal().read(cx).title().into())
}
fn tab_content<T: 'static>(
&self,
_detail: Option<usize>,
cx: &gpui::AppContext,
) -> AnyElement<T> {
fn tab_content(&self, _detail: Option<usize>, cx: &WindowContext) -> AnyElement {
let title = self.terminal().read(cx).title();
div()
@@ -795,7 +782,7 @@ impl Item for TerminalView {
// }
fn breadcrumb_location(&self) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft { flex: None }
ToolbarItemLocation::PrimaryLeft
}
fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+3 -2
View File
@@ -5,9 +5,9 @@ edition = "2021"
publish = false
[features]
default = ["stories"]
default = []
importing-themes = []
stories = ["dep:itertools"]
stories = ["dep:itertools", "dep:story"]
test-support = [
"gpui/test-support",
"fs/test-support",
@@ -30,6 +30,7 @@ serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
settings = { package = "settings2", path = "../settings2" }
story = { path = "../story", optional = true }
toml.workspace = true
uuid.workspace = true
util = { path = "../util" }
+1 -1
View File
@@ -184,7 +184,7 @@ impl settings::Settings for ThemeSettings {
) -> schemars::schema::RootSchema {
let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
let theme_names = cx
.global::<Arc<ThemeRegistry>>()
.global::<ThemeRegistry>()
.list_names(params.staff_mode)
.map(|theme_name| Value::String(theme_name.to_string()))
.collect();
-41
View File
@@ -1,41 +0,0 @@
use gpui::{div, Div, Element, ParentElement, SharedString, Styled, ViewContext};
use crate::ActiveTheme;
pub struct Story {}
impl Story {
pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
div()
.size_full()
.flex()
.flex_col()
.pt_2()
.px_4()
.font("Zed Mono")
.bg(cx.theme().colors().background)
}
pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: SharedString) -> impl Element<V> {
div()
.text_xl()
.text_color(cx.theme().colors().text)
.child(title)
}
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
Self::title(cx, std::any::type_name::<T>().into())
}
pub fn label<V: 'static>(
cx: &mut ViewContext<V>,
label: impl Into<SharedString>,
) -> impl Element<V> {
div()
.mt_4()
.mb_2()
.text_xs()
.text_color(cx.theme().colors().text)
.child(label.into())
}
}
+6
View File
@@ -4,8 +4,14 @@ mod status;
mod syntax;
mod system;
#[cfg(feature = "stories")]
mod stories;
pub use colors::*;
pub use players::*;
pub use status::*;
pub use syntax::*;
pub use system::*;
#[cfg(feature = "stories")]
pub use stories::*;
+2 -138
View File
@@ -1,5 +1,7 @@
use gpui::Hsla;
use crate::{amber, blue, jade, lime, orange, pink, purple, red};
#[derive(Debug, Clone, Copy, Default)]
pub struct PlayerColor {
pub cursor: Hsla,
@@ -133,141 +135,3 @@ impl PlayerColors {
self.0[(participant_index as usize % len) + 1]
}
}
#[cfg(feature = "stories")]
pub use stories::*;
use crate::{amber, blue, jade, lime, orange, pink, purple, red};
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{ActiveTheme, Story};
use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
pub struct PlayerStory;
impl Render<Self> for PlayerStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx).child(
div()
.flex()
.flex_col()
.gap_4()
.child(Story::title_for::<_, PlayerColors>(cx))
.child(Story::label(cx, "Player Colors"))
.child(
div()
.flex()
.flex_col()
.gap_1()
.child(
div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.cursor)
}),
),
)
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.background)
}),
))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.selection)
}),
)),
)
.child(Story::label(cx, "Avatar Rings"))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.my_1()
.rounded_full()
.border_2()
.border_color(player.cursor)
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size_6()
.bg(gpui::red()),
)
}),
))
.child(Story::label(cx, "Player Backgrounds"))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.my_1()
.rounded_xl()
.flex()
.items_center()
.h_8()
.py_0p5()
.px_1p5()
.bg(player.background)
.child(
div().relative().neg_mx_1().rounded_full().z_index(3)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
).child(
div().relative().neg_mx_1().rounded_full().z_index(2)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
).child(
div().relative().neg_mx_1().rounded_full().z_index(1)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
)
}),
))
.child(Story::label(cx, "Player Selections"))
.child(div().flex().flex_col().gap_px().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.flex()
.child(
div()
.flex()
.flex_none()
.rounded_sm()
.px_0p5()
.text_color(cx.theme().colors().text)
.bg(player.selection)
.child("The brown fox jumped over the lazy dog."),
)
.child(div().flex_1())
}),
)),
)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
use gpui::prelude::*;
use gpui::{div, px, Div, ViewContext};
use story::Story;
use crate::{default_color_scales, ColorScaleStep};
pub struct ColorsStory;
impl Render for ColorsStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let color_scales = default_color_scales();
Story::container().child(Story::title("Colors")).child(
div()
.id("colors")
.flex()
.flex_col()
.gap_1()
.overflow_y_scroll()
.text_color(gpui::white())
.children(color_scales.into_iter().map(|scale| {
div()
.flex()
.child(
div()
.w(px(75.))
.line_height(px(24.))
.child(scale.name().clone()),
)
.child(
div().flex().gap_1().children(
ColorScaleStep::ALL
.map(|step| div().flex().size_6().bg(scale.step(cx, step))),
),
)
})),
)
}
}
+5
View File
@@ -0,0 +1,5 @@
mod color;
mod players;
pub use color::*;
pub use players::*;
+137
View File
@@ -0,0 +1,137 @@
use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
use story::Story;
use crate::{ActiveTheme, PlayerColors};
pub struct PlayerStory;
impl Render for PlayerStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container().child(
div()
.flex()
.flex_col()
.gap_4()
.child(Story::title_for::<PlayerColors>())
.child(Story::label("Player Colors"))
.child(
div()
.flex()
.flex_col()
.gap_1()
.child(
div().flex().gap_1().children(
cx.theme()
.players()
.0
.clone()
.iter_mut()
.map(|player| div().w_8().h_8().rounded_md().bg(player.cursor)),
),
)
.child(
div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.background)
}),
),
)
.child(
div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.selection)
}),
),
),
)
.child(Story::label("Avatar Rings"))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.my_1()
.rounded_full()
.border_2()
.border_color(player.cursor)
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size_6()
.bg(gpui::red()),
)
}),
))
.child(Story::label("Player Backgrounds"))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.my_1()
.rounded_xl()
.flex()
.items_center()
.h_8()
.py_0p5()
.px_1p5()
.bg(player.background)
.child(
div().relative().neg_mx_1().rounded_full().z_index(3)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
).child(
div().relative().neg_mx_1().rounded_full().z_index(2)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
).child(
div().relative().neg_mx_1().rounded_full().z_index(1)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
)
}),
))
.child(Story::label("Player Selections"))
.child(div().flex().flex_col().gap_px().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.flex()
.child(
div()
.flex()
.flex_none()
.rounded_sm()
.px_0p5()
.text_color(cx.theme().colors().text)
.bg(player.selection)
.child("The brown fox jumped over the lazy dog."),
)
.child(div().flex_1())
}),
)),
)
}
}
+6 -5
View File
@@ -63,6 +63,12 @@ impl ActiveTheme for AppContext {
}
}
// impl<'a> ActiveTheme for WindowContext<'a> {
// fn theme(&self) -> &Arc<Theme> {
// &ThemeSettings::get_global(self.app()).active_theme
// }
// }
pub struct ThemeFamily {
pub id: String,
pub name: SharedString,
@@ -138,8 +144,3 @@ pub struct DiagnosticStyle {
pub hint: Hsla,
pub ignored: Hsla,
}
#[cfg(feature = "stories")]
mod story;
#[cfg(feature = "stories")]
pub use story::*;
+6 -1
View File
@@ -4,6 +4,10 @@ version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "ui2"
path = "src/ui2.rs"
[dependencies]
anyhow.workspace = true
chrono = "0.4"
@@ -13,10 +17,11 @@ menu = { package = "menu2", path = "../menu2"}
serde.workspace = true
settings2 = { path = "../settings2" }
smallvec.workspace = true
story = { path = "../story", optional = true }
strum = { version = "0.25.0", features = ["derive"] }
theme2 = { path = "../theme2" }
rand = "0.8"
[features]
default = []
stories = ["dep:itertools"]
stories = ["dep:itertools", "dep:story"]
+10 -26
View File
@@ -2,56 +2,40 @@ mod avatar;
mod button;
mod checkbox;
mod context_menu;
mod details;
mod disclosure;
mod divider;
mod elevated_surface;
mod facepile;
mod icon;
mod icon_button;
mod indicator;
mod input;
mod keybinding;
mod label;
mod list;
mod modal;
mod notification_toast;
mod palette;
mod panel;
mod player;
mod player_stack;
mod popover;
mod slot;
mod stack;
mod tab;
mod toast;
mod toggle;
mod tool_divider;
mod tooltip;
#[cfg(feature = "stories")]
mod stories;
pub use avatar::*;
pub use button::*;
pub use checkbox::*;
pub use context_menu::*;
pub use details::*;
pub use disclosure::*;
pub use divider::*;
pub use elevated_surface::*;
pub use facepile::*;
pub use icon::*;
pub use icon_button::*;
pub use indicator::*;
pub use input::*;
pub use keybinding::*;
pub use label::*;
pub use list::*;
pub use modal::*;
pub use notification_toast::*;
pub use palette::*;
pub use panel::*;
pub use player::*;
pub use player_stack::*;
pub use popover::*;
pub use slot::*;
pub use stack::*;
pub use tab::*;
pub use toast::*;
pub use toggle::*;
pub use tool_divider::*;
pub use tooltip::*;
#[cfg(feature = "stories")]
pub use stories::*;
+10 -31
View File
@@ -1,16 +1,23 @@
use crate::prelude::*;
use gpui::{img, Img, RenderOnce};
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape {
#[default]
Circle,
RoundedRectangle,
}
#[derive(RenderOnce)]
pub struct Avatar {
src: SharedString,
shape: Shape,
}
impl<V: 'static> Component<V> for Avatar {
type Rendered = Img<V>;
impl Component for Avatar {
type Rendered = Img;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, _: &mut WindowContext) -> Self::Rendered {
let mut img = img();
if self.shape == Shape::Circle {
@@ -39,31 +46,3 @@ impl Avatar {
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct AvatarStory;
impl Render<Self> for AvatarStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<_, Avatar>(cx))
.child(Story::label(cx, "Default"))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/326587?v=4",
))
}
}
}
+42 -270
View File
@@ -1,28 +1,28 @@
use std::sync::Arc;
use std::rc::Rc;
use gpui::{
DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful, StatefulInteractiveElement,
DefiniteLength, Div, Hsla, MouseButton, MouseDownEvent, RenderOnce, StatefulInteractiveElement,
WindowContext,
};
use crate::prelude::*;
use crate::{h_stack, Icon, IconButton, IconElement, Label, LineHeightStyle, TextColor};
use crate::{h_stack, Color, Icon, IconButton, IconElement, Label, LineHeightStyle};
/// Provides the flexibility to use either a standard
/// button or an icon button in a given context.
pub enum ButtonOrIconButton<V: 'static> {
Button(Button<V>),
IconButton(IconButton<V>),
pub enum ButtonOrIconButton {
Button(Button),
IconButton(IconButton),
}
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
fn from(value: Button<V>) -> Self {
impl From<Button> for ButtonOrIconButton {
fn from(value: Button) -> Self {
Self::Button(value)
}
}
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
fn from(value: IconButton<V>) -> Self {
impl From<IconButton> for ButtonOrIconButton {
fn from(value: IconButton) -> Self {
Self::IconButton(value)
}
}
@@ -64,42 +64,26 @@ impl ButtonVariant {
}
}
pub type ClickHandler<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>)>;
struct ButtonHandlers<V: 'static> {
click: Option<ClickHandler<V>>,
}
unsafe impl<S> Send for ButtonHandlers<S> {}
unsafe impl<S> Sync for ButtonHandlers<S> {}
impl<V: 'static> Default for ButtonHandlers<V> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(RenderOnce)]
pub struct Button<V: 'static> {
pub struct Button {
disabled: bool,
handlers: ButtonHandlers<V>,
click_handler: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
icon: Option<Icon>,
icon_position: Option<IconPosition>,
label: SharedString,
variant: ButtonVariant,
width: Option<DefiniteLength>,
color: Option<TextColor>,
color: Option<Color>,
}
impl<V: 'static> Component<V> for Button<V> {
type Rendered = Stateful<V, Div<V>>;
impl Component for Button {
type Rendered = gpui::Stateful<Div>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let _view: &mut V = view;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let (icon_color, label_color) = match (self.disabled, self.color) {
(true, _) => (TextColor::Disabled, TextColor::Disabled),
(_, None) => (TextColor::Default, TextColor::Default),
(_, Some(color)) => (TextColor::from(color), color),
(true, _) => (Color::Disabled, Color::Disabled),
(_, None) => (Color::Default, Color::Default),
(_, Some(color)) => (Color::from(color), color),
};
let mut button = h_stack()
@@ -133,9 +117,9 @@ impl<V: 'static> Component<V> for Button<V> {
button = button.w(width).justify_center();
}
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
click_handler(state, cx);
if let Some(click_handler) = self.click_handler.clone() {
button = button.on_mouse_down(MouseButton::Left, move |event, cx| {
click_handler(event, cx);
});
}
@@ -143,11 +127,11 @@ impl<V: 'static> Component<V> for Button<V> {
}
}
impl<V: 'static> Button<V> {
impl Button {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
disabled: false,
handlers: ButtonHandlers::default(),
click_handler: None,
icon: None,
icon_position: None,
label: label.into(),
@@ -184,8 +168,11 @@ impl<V: 'static> Button<V> {
self
}
pub fn on_click(mut self, handler: ClickHandler<V>) -> Self {
self.handlers.click = Some(handler);
pub fn on_click(
mut self,
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) -> Self {
self.click_handler = Some(Rc::new(handler));
self
}
@@ -194,14 +181,14 @@ impl<V: 'static> Button<V> {
self
}
pub fn color(mut self, color: Option<TextColor>) -> Self {
pub fn color(mut self, color: Option<Color>) -> Self {
self.color = color;
self
}
pub fn label_color(&self, color: Option<TextColor>) -> TextColor {
pub fn label_color(&self, color: Option<Color>) -> Color {
if self.disabled {
TextColor::Disabled
Color::Disabled
} else if let Some(color) = color {
color
} else {
@@ -209,253 +196,38 @@ impl<V: 'static> Button<V> {
}
}
fn render_label(&self, color: TextColor) -> Label {
fn render_label(&self, color: Color) -> Label {
Label::new(self.label.clone())
.color(color)
.line_height_style(LineHeightStyle::UILabel)
}
fn render_icon(&self, icon_color: TextColor) -> Option<IconElement> {
fn render_icon(&self, icon_color: Color) -> Option<IconElement> {
self.icon.map(|i| IconElement::new(i).color(icon_color))
}
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let (icon_color, label_color) = match (self.disabled, self.color) {
(true, _) => (TextColor::Disabled, TextColor::Disabled),
(_, None) => (TextColor::Default, TextColor::Default),
(_, Some(color)) => (TextColor::from(color), color),
};
let mut button = h_stack()
.id(SharedString::from(format!("{}", self.label)))
.relative()
.p_1()
.text_ui()
.rounded_md()
.bg(self.variant.bg_color(cx))
.cursor_pointer()
.hover(|style| style.bg(self.variant.bg_color_hover(cx)))
.active(|style| style.bg(self.variant.bg_color_active(cx)));
match (self.icon, self.icon_position) {
(Some(_), Some(IconPosition::Left)) => {
button = button
.gap_1()
.child(self.render_label(label_color))
.children(self.render_icon(icon_color))
}
(Some(_), Some(IconPosition::Right)) => {
button = button
.gap_1()
.children(self.render_icon(icon_color))
.child(self.render_label(label_color))
}
(_, _) => button = button.child(self.render_label(label_color)),
}
if let Some(width) = self.width {
button = button.w(width).justify_center();
}
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
click_handler(state, cx);
});
}
button
}
}
#[derive(RenderOnce)]
pub struct ButtonGroup<V: 'static> {
buttons: Vec<Button<V>>,
pub struct ButtonGroup {
buttons: Vec<Button>,
}
impl<V: 'static> Component<V> for ButtonGroup<V> {
type Rendered = Div<V>;
impl Component for ButtonGroup {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let mut group = h_stack();
for button in self.buttons.into_iter() {
group = group.child(button.render(view, cx));
group = group.child(button.render(cx));
}
group
}
}
impl<V: 'static> ButtonGroup<V> {
pub fn new(buttons: Vec<Button<V>>) -> Self {
impl ButtonGroup {
pub fn new(buttons: Vec<Button>) -> Self {
Self { buttons }
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{h_stack, v_stack, Story, TextColor};
use gpui::{rems, Div, Render};
use strum::IntoEnumIterator;
pub struct ButtonStory;
impl Render<Self> for ButtonStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let states = InteractionState::iter();
Story::container(cx)
.child(Story::title_for::<_, Button<Self>>(cx))
.child(
div()
.flex()
.gap_8()
.child(
div()
.child(Story::label(cx, "Ghost (Default)"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
)
})))
.child(Story::label(cx, "Ghost Left Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.icon(Icon::Plus)
.icon_position(IconPosition::Left), // .state(state),
)
})))
.child(Story::label(cx, "Ghost Right Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.icon(Icon::Plus)
.icon_position(IconPosition::Right), // .state(state),
)
}))),
)
.child(
div()
.child(Story::label(cx, "Filled"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
)
})))
.child(Story::label(cx, "Filled Left Button"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
.icon(Icon::Plus)
.icon_position(IconPosition::Left), // .state(state),
)
})))
.child(Story::label(cx, "Filled Right Button"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
.icon(Icon::Plus)
.icon_position(IconPosition::Right), // .state(state),
)
}))),
)
.child(
div()
.child(Story::label(cx, "Fixed With"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.width(Some(rems(6.).into())),
)
})))
.child(Story::label(cx, "Fixed With Left Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.icon(Icon::Plus)
.icon_position(IconPosition::Left)
.width(Some(rems(6.).into())),
)
})))
.child(Story::label(cx, "Fixed With Right Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.icon(Icon::Plus)
.icon_position(IconPosition::Right)
.width(Some(rems(6.).into())),
)
}))),
),
)
.child(Story::label(cx, "Button with `on_click`"))
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.on_click(Arc::new(|_view, _cx| println!("Button clicked."))),
)
}
}
}
+23 -87
View File
@@ -1,10 +1,10 @@
use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Stateful, Styled, ViewContext};
use std::sync::Arc;
use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Styled, WindowContext};
use theme2::ActiveTheme;
use crate::{Icon, IconElement, Selection, TextColor};
use crate::{Color, Icon, IconElement, Selection};
pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) + Send + Sync>;
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
/// # Checkbox
///
@@ -12,17 +12,17 @@ pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) +
/// Each checkbox works independently from other checkboxes in the list,
/// therefore checking an additional box does not affect any other selections.
#[derive(RenderOnce)]
pub struct Checkbox<V: 'static> {
pub struct Checkbox {
id: ElementId,
checked: Selection,
disabled: bool,
on_click: Option<CheckHandler<V>>,
on_click: Option<CheckHandler>,
}
impl<V: 'static> Component<V> for Checkbox<V> {
type Rendered = Stateful<V, Div<V>>;
impl Component for Checkbox {
type Rendered = gpui::Stateful<Div>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
@@ -34,9 +34,9 @@ impl<V: 'static> Component<V> for Checkbox<V> {
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
Color::Disabled
} else {
TextColor::Selected
Color::Selected
},
),
)
@@ -49,9 +49,9 @@ impl<V: 'static> Component<V> for Checkbox<V> {
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
Color::Disabled
} else {
TextColor::Selected
Color::Selected
},
),
)
@@ -137,13 +137,11 @@ impl<V: 'static> Component<V> for Checkbox<V> {
)
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
},
|this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
)
}
}
impl<V: 'static> Checkbox<V> {
impl Checkbox {
pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
Self {
id: id.into(),
@@ -160,13 +158,13 @@ impl<V: 'static> Checkbox<V> {
pub fn on_click(
mut self,
handler: impl 'static + Fn(Selection, &mut V, &mut ViewContext<V>) + Send + Sync,
handler: impl 'static + Fn(&Selection, &mut WindowContext) + Send + Sync,
) -> Self {
self.on_click = Some(Arc::new(handler));
self.on_click = Some(Box::new(handler));
self
}
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
pub fn render(self, cx: &mut WindowContext) -> impl Element {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
@@ -178,9 +176,9 @@ impl<V: 'static> Checkbox<V> {
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
Color::Disabled
} else {
TextColor::Selected
Color::Selected
},
),
)
@@ -193,9 +191,9 @@ impl<V: 'static> Checkbox<V> {
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
Color::Disabled
} else {
TextColor::Selected
Color::Selected
},
),
)
@@ -281,69 +279,7 @@ impl<V: 'static> Checkbox<V> {
)
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
},
|this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
)
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{h_stack, Story};
use gpui::{Div, Render};
pub struct CheckboxStory;
impl Render<Self> for CheckboxStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<_, Checkbox<Self>>(cx))
.child(Story::label(cx, "Default"))
.child(
h_stack()
.p_2()
.gap_2()
.rounded_md()
.border()
.border_color(cx.theme().colors().border)
.child(Checkbox::new("checkbox-enabled", Selection::Unselected))
.child(Checkbox::new(
"checkbox-intermediate",
Selection::Indeterminate,
))
.child(Checkbox::new("checkbox-selected", Selection::Selected)),
)
.child(Story::label(cx, "Disabled"))
.child(
h_stack()
.p_2()
.gap_2()
.rounded_md()
.border()
.border_color(cx.theme().colors().border)
.child(
Checkbox::new("checkbox-disabled", Selection::Unselected)
.disabled(true),
)
.child(
Checkbox::new(
"checkbox-disabled-intermediate",
Selection::Indeterminate,
)
.disabled(true),
)
.child(
Checkbox::new("checkbox-disabled-selected", Selection::Selected)
.disabled(true),
),
)
}
}
}
+66 -187
View File
@@ -1,47 +1,42 @@
use std::cell::RefCell;
use std::rc::Rc;
use crate::{prelude::*, v_stack, List};
use crate::{ListEntry, ListSeparator, ListSubHeader};
use crate::{prelude::*, v_stack, Label, List};
use crate::{ListItem, ListSeparator, ListSubHeader};
use gpui::{
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView,
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase,
Div, EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext,
};
pub enum ContextMenuItem<V: 'static> {
Separator(ListSeparator),
Header(ListSubHeader),
Entry(
ListEntry<ContextMenu<V>>,
Rc<dyn Fn(&mut V, &mut ViewContext<V>)>,
),
pub enum ContextMenuItem {
Separator,
Header(SharedString),
Entry(SharedString, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
}
pub struct ContextMenu<V: 'static> {
items: Vec<ContextMenuItem<V>>,
pub struct ContextMenu {
items: Vec<ContextMenuItem>,
focus_handle: FocusHandle,
handle: WeakView<V>,
}
impl<V: 'static> FocusableView for ContextMenu<V> {
impl FocusableView for ContextMenu {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl<V: 'static> EventEmitter<Manager> for ContextMenu<V> {}
impl EventEmitter<Manager> for ContextMenu {}
impl<V: 'static> ContextMenu<V> {
impl ContextMenu {
pub fn build(
cx: &mut ViewContext<V>,
f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
cx: &mut WindowContext,
f: impl FnOnce(Self, &mut WindowContext) -> Self,
) -> View<Self> {
let handle = cx.view().downgrade();
// let handle = cx.view().downgrade();
cx.build_view(|cx| {
f(
Self {
handle,
items: Default::default(),
focus_handle: cx.focus_handle(),
},
@@ -51,29 +46,30 @@ impl<V: 'static> ContextMenu<V> {
}
pub fn header(mut self, title: impl Into<SharedString>) -> Self {
self.items
.push(ContextMenuItem::Header(ListSubHeader::new(title)));
self.items.push(ContextMenuItem::Header(title.into()));
self
}
pub fn separator(mut self) -> Self {
self.items.push(ContextMenuItem::Separator(ListSeparator));
self.items.push(ContextMenuItem::Separator);
self
}
pub fn entry(
mut self,
view: ListEntry<Self>,
on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static,
label: impl Into<SharedString>,
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
) -> Self {
self.items
.push(ContextMenuItem::Entry(view, Rc::new(on_click)));
.push(ContextMenuItem::Entry(label.into(), Rc::new(on_click)));
self
}
pub fn action(self, view: ListEntry<Self>, action: Box<dyn Action>) -> Self {
pub fn action(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
// todo: add the keybindings to the list entry
self.entry(view, move |_, cx| cx.dispatch_action(action.boxed_clone()))
self.entry(label.into(), move |_, cx| {
cx.dispatch_action(action.boxed_clone())
})
}
pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -86,39 +82,42 @@ impl<V: 'static> ContextMenu<V> {
}
}
impl<V: 'static> Render<Self> for ContextMenu<V> {
type Element = Div<Self>;
impl Render for ContextMenu {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div().elevation_2(cx).flex().flex_row().child(
v_stack()
.min_w(px(200.))
.track_focus(&self.focus_handle)
.on_mouse_down_out(|this: &mut Self, _, cx| this.cancel(&Default::default(), cx))
.on_mouse_down_out(
cx.listener(|this: &mut Self, _, cx| this.cancel(&Default::default(), cx)),
)
// .on_action(ContextMenu::select_first)
// .on_action(ContextMenu::select_last)
// .on_action(ContextMenu::select_next)
// .on_action(ContextMenu::select_prev)
.on_action(ContextMenu::confirm)
.on_action(ContextMenu::cancel)
.on_action(cx.listener(ContextMenu::confirm))
.on_action(cx.listener(ContextMenu::cancel))
.flex_none()
// .bg(cx.theme().colors().elevated_surface_background)
// .border()
// .border_color(cx.theme().colors().border)
.child(
List::new().children(self.items.iter().map(|item| match item {
ContextMenuItem::Separator(separator) => {
separator.clone().render_into_any()
ContextMenuItem::Separator => ListSeparator::new().render_into_any(),
ContextMenuItem::Header(header) => {
ListSubHeader::new(header.clone()).render_into_any()
}
ContextMenuItem::Header(header) => header.clone().render_into_any(),
ContextMenuItem::Entry(entry, callback) => {
let callback = callback.clone();
let handle = self.handle.clone();
entry
.clone()
.on_click(move |this, cx| {
handle.update(cx, |view, cx| callback(view, cx)).ok();
cx.emit(Manager::Dismiss);
let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss));
ListItem::new(entry.clone())
.child(Label::new(entry.clone()))
.on_click(move |event, cx| {
callback(event, cx);
dismiss(event, cx)
})
.render_into_any()
}
@@ -128,22 +127,21 @@ impl<V: 'static> Render<Self> for ContextMenu<V> {
}
}
pub struct MenuHandle<V: 'static, M: ManagedView> {
pub struct MenuHandle<M: ManagedView> {
id: ElementId,
child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement<V> + 'static>>,
menu_builder: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static>>,
child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement + 'static>>,
menu_builder: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
anchor: Option<AnchorCorner>,
attach: Option<AnchorCorner>,
}
impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static) -> Self {
impl<M: ManagedView> MenuHandle<M> {
pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View<M> + 'static) -> Self {
self.menu_builder = Some(Rc::new(f));
self
}
pub fn child<R: RenderOnce<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
pub fn child<R: RenderOnce>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
self.child_builder = Some(Box::new(|b| f(b).render_once().into_any()));
self
}
@@ -162,7 +160,7 @@ impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
}
}
pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<V, M> {
pub fn menu_handle<M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<M> {
MenuHandle {
id: id.into(),
child_builder: None,
@@ -172,21 +170,20 @@ pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> Menu
}
}
pub struct MenuHandleState<V, M> {
pub struct MenuHandleState<M> {
menu: Rc<RefCell<Option<View<M>>>>,
position: Rc<RefCell<Point<Pixels>>>,
child_layout_id: Option<LayoutId>,
child_element: Option<AnyElement<V>>,
menu_element: Option<AnyElement<V>>,
child_element: Option<AnyElement>,
menu_element: Option<AnyElement>,
}
impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
type State = MenuHandleState<V, M>;
impl<M: ManagedView> Element for MenuHandle<M> {
type State = MenuHandleState<M>;
fn layout(
&mut self,
view_state: &mut V,
element_state: Option<Self::State>,
cx: &mut crate::ViewContext<V>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::State) {
let (menu, position) = if let Some(element_state) = element_state {
(element_state.menu, element_state.position)
@@ -197,14 +194,14 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
let mut menu_layout_id = None;
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
let mut overlay = overlay::<V>().snap_to_window();
let mut overlay = overlay().snap_to_window();
if let Some(anchor) = self.anchor {
overlay = overlay.anchor(anchor);
}
overlay = overlay.position(*position.borrow());
let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(element.layout(view_state, cx));
menu_layout_id = Some(element.layout(cx));
element
});
@@ -215,7 +212,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.layout(view_state, cx));
.map(|child_element| child_element.layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
@@ -237,16 +234,15 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
fn paint(
self,
bounds: Bounds<gpui::Pixels>,
view_state: &mut V,
element_state: &mut Self::State,
cx: &mut crate::ViewContext<V>,
cx: &mut WindowContext,
) {
if let Some(child) = element_state.child_element.take() {
child.paint(view_state, cx);
child.paint(cx);
}
if let Some(menu) = element_state.menu_element.take() {
menu.paint(view_state, cx);
menu.paint(cx);
return;
}
@@ -258,7 +254,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
let attach = self.attach.clone();
let child_layout_id = element_state.child_layout_id.clone();
cx.on_mouse_event(move |view_state, event: &MouseDownEvent, phase, cx| {
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == MouseButton::Right
&& bounds.contains_point(&event.position)
@@ -266,9 +262,9 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
cx.stop_propagation();
cx.prevent_default();
let new_menu = (builder)(view_state, cx);
let new_menu = (builder)(cx);
let menu2 = menu.clone();
cx.subscribe(&new_menu, move |this, modal, e, cx| match e {
cx.subscribe(&new_menu, move |modal, e, cx| match e {
&Manager::Dismiss => {
*menu2.borrow_mut() = None;
cx.notify();
@@ -291,7 +287,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
}
}
impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
impl<M: ManagedView> RenderOnce for MenuHandle<M> {
type Element = Self;
fn element_id(&self) -> Option<gpui::ElementId> {
@@ -302,120 +298,3 @@ impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{story::Story, Label};
use gpui::{actions, Div, Render};
actions!(PrintCurrentDate, PrintBestFood);
fn build_menu<V: Render<V>>(
cx: &mut ViewContext<V>,
header: impl Into<SharedString>,
) -> View<ContextMenu<V>> {
let handle = cx.view().clone();
ContextMenu::build(cx, |menu, _| {
menu.header(header)
.separator()
.entry(
ListEntry::new("Print current time", Label::new("Print current time")),
|v, cx| {
println!("dispatching PrintCurrentTime action");
cx.dispatch_action(PrintCurrentDate.boxed_clone())
},
)
.entry(
ListEntry::new("Print best food", Label::new("Print best food")),
|v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
)
})
}
pub struct ContextMenuStory;
impl Render<Self> for ContextMenuStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.on_action(|_, _: &PrintCurrentDate, _| {
println!("printing unix time!");
if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
println!("Current Unix time is {:?}", unix_time.as_secs());
}
})
.on_action(|_, _: &PrintBestFood, _| {
println!("burrito");
})
.flex()
.flex_row()
.justify_between()
.child(
div()
.flex()
.flex_col()
.justify_between()
.child(
menu_handle("test2")
.child(|is_open| {
Label::new(if is_open {
"TOP LEFT"
} else {
"RIGHT CLICK ME"
})
})
.menu(move |_, cx| build_menu(cx, "top left")),
)
.child(
menu_handle("test1")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM LEFT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::BottomLeft)
.attach(AnchorCorner::TopLeft)
.menu(move |_, cx| build_menu(cx, "bottom left")),
),
)
.child(
div()
.flex()
.flex_col()
.justify_between()
.child(
menu_handle("test3")
.child(|is_open| {
Label::new(if is_open {
"TOP RIGHT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::TopRight)
.menu(move |_, cx| build_menu(cx, "top right")),
)
.child(
menu_handle("test4")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM RIGHT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::BottomRight)
.attach(AnchorCorner::TopRight)
.menu(move |_, cx| build_menu(cx, "bottom right")),
),
)
}
}
}
-83
View File
@@ -1,83 +0,0 @@
use crate::prelude::*;
use crate::{v_stack, ButtonGroup};
#[derive(RenderOnce)]
pub struct Details<V: 'static> {
text: &'static str,
meta: Option<&'static str>,
actions: Option<ButtonGroup<V>>,
}
impl<V: 'static> Component<V> for Details<V> {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.p_1()
.gap_0p5()
.text_ui_sm()
.text_color(cx.theme().colors().text)
.size_full()
.child(self.text)
.children(self.meta.map(|m| m))
.children(self.actions.map(|a| a))
}
}
impl<V: 'static> Details<V> {
pub fn new(text: &'static str) -> Self {
Self {
text,
meta: None,
actions: None,
}
}
pub fn meta_text(mut self, meta: &'static str) -> Self {
self.meta = Some(meta);
self
}
pub fn actions(mut self, actions: ButtonGroup<V>) -> Self {
self.actions = Some(actions);
self
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{Button, Story};
use gpui::{Div, Render};
pub struct DetailsStory;
impl Render<Self> for DetailsStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<_, Details<Self>>(cx))
.child(Story::label(cx, "Default"))
.child(Details::new("The quick brown fox jumps over the lazy dog"))
.child(Story::label(cx, "With meta"))
.child(
Details::new("The quick brown fox jumps over the lazy dog")
.meta_text("Sphinx of black quartz, judge my vow."),
)
.child(Story::label(cx, "With meta and actions"))
.child(
Details::new("The quick brown fox jumps over the lazy dog")
.meta_text("Sphinx of black quartz, judge my vow.")
.actions(ButtonGroup::new(vec![
Button::new("Decline"),
Button::new("Accept").variant(crate::ButtonVariant::Filled),
])),
)
}
}
}
+19
View File
@@ -0,0 +1,19 @@
use gpui::{div, Element, ParentElement};
use crate::{Color, Icon, IconElement, IconSize, Toggle};
pub fn disclosure_control(toggle: Toggle) -> impl Element {
match (toggle.is_toggleable(), toggle.is_toggled()) {
(false, _) => div(),
(_, true) => div().child(
IconElement::new(Icon::ChevronDown)
.color(Color::Muted)
.size(IconSize::Small),
),
(_, false) => div().child(
IconElement::new(Icon::ChevronRight)
.color(Color::Muted)
.size(IconSize::Small),
),
}
}
+4 -4
View File
@@ -13,10 +13,10 @@ pub struct Divider {
inset: bool,
}
impl<V: 'static> Component<V> for Divider {
type Rendered = Div<V>;
impl Component for Divider {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
@@ -50,7 +50,7 @@ impl Divider {
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
fn render(self, cx: &mut WindowContext) -> impl Element {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
@@ -1,28 +0,0 @@
use gpui::Div;
use crate::{prelude::*, v_stack};
/// Create an elevated surface.
///
/// Must be used inside of a relative parent element
pub fn elevated_surface<V: 'static>(level: ElevationIndex, cx: &mut ViewContext<V>) -> Div<V> {
let colors = cx.theme().colors();
// let shadow = BoxShadow {
// color: hsla(0., 0., 0., 0.1),
// offset: point(px(0.), px(1.)),
// blur_radius: px(3.),
// spread_radius: px(0.),
// };
v_stack()
.rounded_lg()
.bg(colors.elevated_surface_background)
.border()
.border_color(colors.border)
.shadow(level.shadow())
}
pub fn modal<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
elevated_surface(ElevationIndex::ModalSurface, cx)
}
-64
View File
@@ -1,64 +0,0 @@
use crate::prelude::*;
use crate::{Avatar, Player};
#[derive(RenderOnce)]
pub struct Facepile {
players: Vec<Player>,
}
impl<V: 'static> Component<V> for Facepile {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let player_count = self.players.len();
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
.when(isnt_last, |div| div.neg_mr_1())
.child(Avatar::new(player.avatar_src().to_string()))
});
div().p_1().flex().items_center().children(player_list)
}
}
impl Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
players: players.collect(),
}
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{static_players, Story};
use gpui::{Div, Render};
pub struct FacepileStory;
impl Render<Self> for FacepileStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let players = static_players();
Story::container(cx)
.child(Story::title_for::<_, Facepile>(cx))
.child(Story::label(cx, "Default"))
.child(
div()
.flex()
.gap_3()
.child(Facepile::new(players.clone().into_iter().take(1)))
.child(Facepile::new(players.clone().into_iter().take(2)))
.child(Facepile::new(players.clone().into_iter().take(3))),
)
}
}
}
+12 -36
View File
@@ -23,6 +23,7 @@ pub enum Icon {
BellOff,
BellRing,
Bolt,
CaseSensitive,
Check,
ChevronDown,
ChevronLeft,
@@ -65,6 +66,7 @@ pub enum Icon {
Split,
SplitMessage,
Terminal,
WholeWord,
XCircle,
}
@@ -82,6 +84,7 @@ impl Icon {
Icon::BellOff => "icons/bell-off.svg",
Icon::BellRing => "icons/bell-ring.svg",
Icon::Bolt => "icons/bolt.svg",
Icon::CaseSensitive => "icons/case_insensitive.svg",
Icon::Check => "icons/check.svg",
Icon::ChevronDown => "icons/chevron_down.svg",
Icon::ChevronLeft => "icons/chevron_left.svg",
@@ -124,6 +127,7 @@ impl Icon {
Icon::Split => "icons/split.svg",
Icon::SplitMessage => "icons/split_message.svg",
Icon::Terminal => "icons/terminal.svg",
Icon::WholeWord => "icons/word_search.svg",
Icon::XCircle => "icons/error.svg",
}
}
@@ -132,14 +136,14 @@ impl Icon {
#[derive(RenderOnce)]
pub struct IconElement {
path: SharedString,
color: TextColor,
color: Color,
size: IconSize,
}
impl<V: 'static> Component<V> for IconElement {
type Rendered = Svg<V>;
impl Component for IconElement {
type Rendered = Svg;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
@@ -157,7 +161,7 @@ impl IconElement {
pub fn new(icon: Icon) -> Self {
Self {
path: icon.path().into(),
color: TextColor::default(),
color: Color::default(),
size: IconSize::default(),
}
}
@@ -165,12 +169,12 @@ impl IconElement {
pub fn from_path(path: impl Into<SharedString>) -> Self {
Self {
path: path.into(),
color: TextColor::default(),
color: Color::default(),
size: IconSize::default(),
}
}
pub fn color(mut self, color: TextColor) -> Self {
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
@@ -180,7 +184,7 @@ impl IconElement {
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
fn render(self, cx: &mut WindowContext) -> impl Element {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
@@ -193,31 +197,3 @@ impl IconElement {
.text_color(self.color.color(cx))
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui::{Div, Render};
use strum::IntoEnumIterator;
use crate::Story;
use super::*;
pub struct IconStory;
impl Render<Self> for IconStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let icons = Icon::iter();
Story::container(cx)
.child(Story::title_for::<_, IconElement>(cx))
.child(Story::label(cx, "All Icons"))
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
}
}
}
+25 -36
View File
@@ -1,36 +1,25 @@
use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement};
use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful};
use std::sync::Arc;
struct IconButtonHandlers<V: 'static> {
click: Option<ClickHandler<V>>,
}
impl<V: 'static> Default for IconButtonHandlers<V> {
fn default() -> Self {
Self { click: None }
}
}
use crate::{h_stack, prelude::*, Icon, IconElement};
use gpui::{prelude::*, Action, AnyView, Div, MouseButton, MouseDownEvent, Stateful};
#[derive(RenderOnce)]
pub struct IconButton<V: 'static> {
pub struct IconButton {
id: ElementId,
icon: Icon,
color: TextColor,
color: Color,
variant: ButtonVariant,
state: InteractionState,
selected: bool,
tooltip: Option<Box<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>>,
handlers: IconButtonHandlers<V>,
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
on_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
}
impl<V: 'static> Component<V> for IconButton<V> {
type Rendered = Stateful<V, Div<V>>;
impl Component for IconButton {
type Rendered = Stateful<Div>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let icon_color = match (self.state, self.color) {
(InteractionState::Disabled, _) => TextColor::Disabled,
(InteractionState::Active, _) => TextColor::Selected,
(InteractionState::Disabled, _) => Color::Disabled,
(InteractionState::Active, _) => Color::Selected,
_ => self.color,
};
@@ -65,16 +54,16 @@ impl<V: 'static> Component<V> for IconButton<V> {
.active(|style| style.bg(bg_active_color))
.child(IconElement::new(self.icon).color(icon_color));
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
if let Some(click_handler) = self.on_mouse_down {
button = button.on_mouse_down(MouseButton::Left, move |event, cx| {
cx.stop_propagation();
click_handler(state, cx);
click_handler(event, cx);
})
}
if let Some(tooltip) = self.tooltip {
if !self.selected {
button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
button = button.tooltip(move |cx| tooltip(cx))
}
}
@@ -82,17 +71,17 @@ impl<V: 'static> Component<V> for IconButton<V> {
}
}
impl<V: 'static> IconButton<V> {
impl IconButton {
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
Self {
id: id.into(),
icon,
color: TextColor::default(),
color: Color::default(),
variant: ButtonVariant::default(),
state: InteractionState::default(),
selected: false,
tooltip: None,
handlers: IconButtonHandlers::default(),
on_mouse_down: None,
}
}
@@ -101,7 +90,7 @@ impl<V: 'static> IconButton<V> {
self
}
pub fn color(mut self, color: TextColor) -> Self {
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
@@ -121,16 +110,16 @@ impl<V: 'static> IconButton<V> {
self
}
pub fn tooltip(
mut self,
tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
) -> Self {
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip));
self
}
pub fn on_click(mut self, handler: impl 'static + Fn(&mut V, &mut ViewContext<V>)) -> Self {
self.handlers.click = Some(Arc::new(handler));
pub fn on_click(
mut self,
handler: impl 'static + Fn(&MouseDownEvent, &mut WindowContext),
) -> Self {
self.on_mouse_down = Some(Box::new(handler));
self
}
-37
View File
@@ -1,37 +0,0 @@
use crate::prelude::*;
use gpui::{px, Div, RenderOnce};
#[derive(RenderOnce)]
pub struct UnreadIndicator;
impl<V: 'static> Component<V> for UnreadIndicator {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.rounded_full()
.border_2()
.border_color(cx.theme().colors().surface_background)
.w(px(9.0))
.h(px(9.0))
.z_index(2)
.bg(cx.theme().status().info)
}
}
impl UnreadIndicator {
pub fn new() -> Self {
Self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div()
.rounded_full()
.border_2()
.border_color(cx.theme().colors().surface_background)
.w(px(9.0))
.h(px(9.0))
.z_index(2)
.bg(cx.theme().status().info)
}
}
+7 -30
View File
@@ -18,10 +18,10 @@ pub struct Input {
is_active: bool,
}
impl<V: 'static> Component<V> for Input {
type Rendered = Stateful<V, Div<V>>;
impl Component for Input {
type Rendered = Stateful<Div>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
InputVariant::Ghost => (
cx.theme().colors().ghost_element_background,
@@ -36,15 +36,15 @@ impl<V: 'static> Component<V> for Input {
};
let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
TextColor::Disabled
Color::Disabled
} else {
TextColor::Placeholder
Color::Placeholder
});
let label = Label::new(self.value.clone()).color(if self.disabled {
TextColor::Disabled
Color::Disabled
} else {
TextColor::Default
Color::Default
});
div()
@@ -106,26 +106,3 @@ impl Input {
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct InputStory;
impl Render<Self> for InputStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<_, Input>(cx))
.child(Story::label(cx, "Default"))
.child(div().flex().child(Input::new("Search")))
}
}
}
+6 -74
View File
@@ -10,10 +10,10 @@ pub struct KeyBinding {
key_binding: gpui::KeyBinding,
}
impl<V: 'static> Component<V> for KeyBinding {
type Rendered = Div<V>;
impl Component for KeyBinding {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.flex()
.gap_2()
@@ -49,11 +49,10 @@ pub struct Key {
key: SharedString,
}
impl<V: 'static> Component<V> for Key {
type Rendered = Div<V>;
impl Component for Key {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let _view: &mut V = view;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.px_2()
.py_0()
@@ -70,70 +69,3 @@ impl Key {
Self { key: key.into() }
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
pub use crate::KeyBinding;
use crate::Story;
use gpui::{actions, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render<Self> for KeybindingStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations =
["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx)
.child(Story::title_for::<_, KeyBinding>(cx))
.child(Story::label(cx, "Single Key"))
.child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier"))
.child(
div()
.flex()
.gap_3()
.child(KeyBinding::new(binding("ctrl-c")))
.child(KeyBinding::new(binding("alt-c")))
.child(KeyBinding::new(binding("cmd-c")))
.child(KeyBinding::new(binding("shift-c"))),
)
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child(
div().flex().flex_col().children(
all_modifier_permutations
.chunks(4)
.into_iter()
.map(|chunk| {
div()
.flex()
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.child(Story::label(cx, "Single Key with All Modifiers"))
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord"))
.child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
.child(KeyBinding::new(binding("ctrl-a shift-z")))
.child(KeyBinding::new(binding("fn-s")))
}
}
}
+14 -88
View File
@@ -9,48 +9,6 @@ pub enum LabelSize {
Small,
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum TextColor {
#[default]
Default,
Accent,
Created,
Deleted,
Disabled,
Error,
Hidden,
Info,
Modified,
Muted,
Placeholder,
Player(u32),
Selected,
Success,
Warning,
}
impl TextColor {
pub fn color(&self, cx: &WindowContext) -> Hsla {
match self {
TextColor::Default => cx.theme().colors().text,
TextColor::Muted => cx.theme().colors().text_muted,
TextColor::Created => cx.theme().status().created,
TextColor::Modified => cx.theme().status().modified,
TextColor::Deleted => cx.theme().status().deleted,
TextColor::Disabled => cx.theme().colors().text_disabled,
TextColor::Hidden => cx.theme().status().hidden,
TextColor::Info => cx.theme().status().info,
TextColor::Placeholder => cx.theme().colors().text_placeholder,
TextColor::Accent => cx.theme().colors().text_accent,
TextColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor,
TextColor::Error => cx.theme().status().error,
TextColor::Selected => cx.theme().colors().text_accent,
TextColor::Success => cx.theme().status().success,
TextColor::Warning => cx.theme().status().warning,
}
}
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum LineHeightStyle {
#[default]
@@ -64,14 +22,14 @@ pub struct Label {
label: SharedString,
size: LabelSize,
line_height_style: LineHeightStyle,
color: TextColor,
color: Color,
strikethrough: bool,
}
impl<V: 'static> Component<V> for Label {
type Rendered = Div<V>;
impl Component for Label {
type Rendered = Div;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.when(self.strikethrough, |this| {
this.relative().child(
@@ -80,7 +38,7 @@ impl<V: 'static> Component<V> for Label {
.top_1_2()
.w_full()
.h_px()
.bg(TextColor::Hidden.color(cx)),
.bg(Color::Hidden.color(cx)),
)
})
.map(|this| match self.size {
@@ -101,7 +59,7 @@ impl Label {
label: label.into(),
size: LabelSize::Default,
line_height_style: LineHeightStyle::default(),
color: TextColor::Default,
color: Color::Default,
strikethrough: false,
}
}
@@ -111,7 +69,7 @@ impl Label {
self
}
pub fn color(mut self, color: TextColor) -> Self {
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
@@ -131,15 +89,15 @@ impl Label {
pub struct HighlightedLabel {
label: SharedString,
size: LabelSize,
color: TextColor,
color: Color,
highlight_indices: Vec<usize>,
strikethrough: bool,
}
impl<V: 'static> Component<V> for HighlightedLabel {
type Rendered = Div<V>;
impl Component for HighlightedLabel {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let highlight_color = cx.theme().colors().text_accent;
let mut text_style = cx.text_style().clone();
@@ -185,7 +143,7 @@ impl<V: 'static> Component<V> for HighlightedLabel {
.my_auto()
.w_full()
.h_px()
.bg(TextColor::Hidden.color(cx)),
.bg(Color::Hidden.color(cx)),
)
})
.map(|this| match self.size {
@@ -203,7 +161,7 @@ impl HighlightedLabel {
Self {
label: label.into(),
size: LabelSize::Default,
color: TextColor::Default,
color: Color::Default,
highlight_indices,
strikethrough: false,
}
@@ -214,7 +172,7 @@ impl HighlightedLabel {
self
}
pub fn color(mut self, color: TextColor) -> Self {
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
@@ -230,35 +188,3 @@ struct Run {
pub text: String,
pub color: Hsla,
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct LabelStory;
impl Render<Self> for LabelStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<_, Label>(cx))
.child(Story::label(cx, "Default"))
.child(Label::new("Hello, world!"))
.child(Story::label(cx, "Highlighted"))
.child(HighlightedLabel::new(
"Hello, world!",
vec![0, 1, 2, 7, 8, 12],
))
.child(HighlightedLabel::new(
"Héllo, world!",
vec![0, 1, 3, 8, 9, 13],
))
}
}
}
+50 -64
View File
@@ -1,8 +1,9 @@
use gpui::{div, AnyElement, Div, RenderOnce, Stateful, StatefulInteractiveElement};
use gpui::{
div, px, AnyElement, ClickEvent, Div, RenderOnce, Stateful, StatefulInteractiveElement,
};
use smallvec::SmallVec;
use std::rc::Rc;
use crate::settings::user_settings;
use crate::{
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
};
@@ -33,10 +34,10 @@ pub struct ListHeader {
toggle: Toggle,
}
impl<V: 'static> Component<V> for ListHeader {
type Rendered = Div<V>;
impl Component for ListHeader {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let disclosure_control = disclosure_control(self.toggle);
let meta = match self.meta {
@@ -46,7 +47,7 @@ impl<V: 'static> Component<V> for ListHeader {
.items_center()
.children(icons.into_iter().map(|i| {
IconElement::new(i)
.color(TextColor::Muted)
.color(Color::Muted)
.size(IconSize::Small)
})),
),
@@ -79,10 +80,10 @@ impl<V: 'static> Component<V> for ListHeader {
.items_center()
.children(self.left_icon.map(|i| {
IconElement::new(i)
.color(TextColor::Muted)
.color(Color::Muted)
.size(IconSize::Small)
}))
.child(Label::new(self.label.clone()).color(TextColor::Muted)),
.child(Label::new(self.label.clone()).color(Color::Muted)),
)
.child(disclosure_control),
)
@@ -118,7 +119,7 @@ impl ListHeader {
}
// before_ship!("delete")
// fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
// fn render<V: 'static>(self, cx: &mut WindowContext) -> impl Element<V> {
// let disclosure_control = disclosure_control(self.toggle);
// let meta = match self.meta {
@@ -200,10 +201,10 @@ impl ListSubHeader {
}
}
impl<V: 'static> Component<V> for ListSubHeader {
type Rendered = Div<V>;
impl Component for ListSubHeader {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
h_stack().flex_1().w_full().relative().py_1().child(
div()
.h_6()
@@ -221,10 +222,10 @@ impl<V: 'static> Component<V> for ListSubHeader {
.items_center()
.children(self.left_icon.map(|i| {
IconElement::new(i)
.color(TextColor::Muted)
.color(Color::Muted)
.size(IconSize::Small)
}))
.child(Label::new(self.label.clone()).color(TextColor::Muted)),
.child(Label::new(self.label.clone()).color(Color::Muted)),
),
)
}
@@ -238,55 +239,38 @@ pub enum ListEntrySize {
}
#[derive(RenderOnce)]
pub struct ListEntry<V: 'static> {
pub struct ListItem {
id: ElementId,
disabled: bool,
// TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility,
indent_level: u32,
label: Label,
left_slot: Option<GraphicSlot>,
overflow: OverflowStyle,
size: ListEntrySize,
toggle: Toggle,
variant: ListItemVariant,
on_click: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) + 'static>>,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>,
}
impl<V> Clone for ListEntry<V> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
disabled: self.disabled,
indent_level: self.indent_level,
label: self.label.clone(),
left_slot: self.left_slot.clone(),
overflow: self.overflow,
size: self.size,
toggle: self.toggle,
variant: self.variant,
on_click: self.on_click.clone(),
}
}
}
impl<V: 'static> ListEntry<V> {
pub fn new(id: impl Into<ElementId>, label: Label) -> Self {
impl ListItem {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
disabled: false,
indent_level: 0,
label,
left_slot: None,
overflow: OverflowStyle::Hidden,
size: ListEntrySize::default(),
toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(),
on_click: Default::default(),
children: SmallVec::new(),
}
}
pub fn on_click(mut self, handler: impl Fn(&mut V, &mut ViewContext<V>) + 'static) -> Self {
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
@@ -327,18 +311,16 @@ impl<V: 'static> ListEntry<V> {
}
}
impl<V: 'static> Component<V> for ListEntry<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let settings = user_settings(cx);
impl Component for ListItem {
type Rendered = Stateful<Div>;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let left_content = match self.left_slot.clone() {
Some(GraphicSlot::Icon(i)) => Some(
h_stack().child(
IconElement::new(i)
.size(IconSize::Small)
.color(TextColor::Muted),
.color(Color::Muted),
),
),
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
@@ -359,26 +341,26 @@ impl<V: 'static> Component<V> for ListEntry<V> {
})
.on_click({
let on_click = self.on_click.clone();
move |view: &mut V, event, cx| {
move |event, cx| {
if let Some(on_click) = &on_click {
(on_click)(view, cx)
(on_click)(event, cx)
}
}
})
.bg(cx.theme().colors().surface_background)
// TODO: Add focus state
// .when(self.state == InteractionState::Focused, |this| {
// this.border()
// .border_color(cx.theme().colors().border_focused)
// })
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
.child(
sized_item
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
// .ml(rems(0.75 * self.indent_level as f32))
.children((0..self.indent_level).map(|_| {
div()
.w(*settings.list_indent_depth)
.w(px(4.))
.h_full()
.flex()
.justify_center()
@@ -395,11 +377,17 @@ impl<V: 'static> Component<V> for ListEntry<V> {
.relative()
.child(disclosure_control(self.toggle))
.children(left_content)
.child(self.label),
.children(self.children),
)
}
}
impl ParentElement for ListItem {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
}
}
#[derive(RenderOnce, Clone)]
pub struct ListSeparator;
@@ -409,34 +397,32 @@ impl ListSeparator {
}
}
impl<V: 'static> Component<V> for ListSeparator {
type Rendered = Div<V>;
impl Component for ListSeparator {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div().h_px().w_full().bg(cx.theme().colors().border_variant)
}
}
#[derive(RenderOnce)]
pub struct List<V: 'static> {
pub struct List {
/// Message to display when the list is empty
/// Defaults to "No items"
empty_message: SharedString,
header: Option<ListHeader>,
toggle: Toggle,
children: SmallVec<[AnyElement<V>; 2]>,
children: SmallVec<[AnyElement; 2]>,
}
impl<V: 'static> Component<V> for List<V> {
type Rendered = Div<V>;
impl Component for List {
type Rendered = Div;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let list_content = match (self.children.is_empty(), self.toggle) {
(false, _) => div().children(self.children),
(true, Toggle::Toggled(false)) => div(),
(true, _) => {
div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
}
(true, _) => div().child(Label::new(self.empty_message.clone()).color(Color::Muted)),
};
v_stack()
@@ -447,7 +433,7 @@ impl<V: 'static> Component<V> for List<V> {
}
}
impl<V: 'static> List<V> {
impl List {
pub fn new() -> Self {
Self {
empty_message: "No items".into(),
@@ -473,8 +459,8 @@ impl<V: 'static> List<V> {
}
}
impl<V: 'static> ParentElement<V> for List<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
impl ParentElement for List {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
}
}
-86
View File
@@ -1,86 +0,0 @@
use gpui::{AnyElement, Div, RenderOnce, Stateful};
use smallvec::SmallVec;
use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
#[derive(RenderOnce)]
pub struct Modal<V: 'static> {
id: ElementId,
title: Option<SharedString>,
primary_action: Option<Button<V>>,
secondary_action: Option<Button<V>>,
children: SmallVec<[AnyElement<V>; 2]>,
}
impl<V: 'static> Component<V> for Modal<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let _view: &mut V = view;
v_stack()
.id(self.id.clone())
.w_96()
// .rounded_xl()
.bg(cx.theme().colors().background)
.border()
.border_color(cx.theme().colors().border)
.shadow_2xl()
.child(
h_stack()
.justify_between()
.p_1()
.border_b()
.border_color(cx.theme().colors().border)
.child(div().children(self.title.clone().map(|t| Label::new(t))))
.child(IconButton::new("close", Icon::Close)),
)
.child(v_stack().p_1().children(self.children))
.when(
self.primary_action.is_some() || self.secondary_action.is_some(),
|this| {
this.child(
h_stack()
.border_t()
.border_color(cx.theme().colors().border)
.p_1()
.justify_end()
.children(self.secondary_action)
.children(self.primary_action),
)
},
)
}
}
impl<V: 'static> Modal<V> {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: None,
primary_action: None,
secondary_action: None,
children: SmallVec::new(),
}
}
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = Some(title.into());
self
}
pub fn primary_action(mut self, action: Button<V>) -> Self {
self.primary_action = Some(action);
self
}
pub fn secondary_action(mut self, action: Button<V>) -> Self {
self.secondary_action = Some(action);
self
}
}
impl<V: 'static> ParentElement<V> for Modal<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}

Some files were not shown because too many files have changed in this diff Show More