Merge branch 'main' into add-app-events
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
+740
-729
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
+310
-366
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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());
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -0,0 +1,3 @@
|
||||
mod story;
|
||||
|
||||
pub use story::*;
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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))
|
||||
})),
|
||||
)
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
pub use ui::Story;
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"}
|
||||
@@ -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.",
|
||||
))),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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())
|
||||
}),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))),
|
||||
),
|
||||
)
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod color;
|
||||
mod players;
|
||||
|
||||
pub use color::*;
|
||||
pub use players::*;
|
||||
@@ -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())
|
||||
}),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."))),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
])),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user