diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 5c7d79f5597edd27e1db4704df4fb780a3b6af4b..e68eab7fc86118fdfe63a4ec467d2e2e5603c7ff 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -1,4 +1,4 @@ -use std::{fmt::Write as _, time::Duration}; +use std::{fmt::Write as _, ops::Range, time::Duration}; use db::anyhow::anyhow; use gpui::{ @@ -15,7 +15,10 @@ use ui::{ }; use workspace::{Item, SerializableItem, Workspace, register_serializable_item}; -use crate::{keybindings::persistence::KEYBINDING_EDITORS, ui_components::table::Table}; +use crate::{ + keybindings::persistence::KEYBINDING_EDITORS, + ui_components::table::{Table, TableInteractionState}, +}; actions!(zed, [OpenKeymapEditor]); @@ -54,6 +57,7 @@ struct KeymapEditor { focus_handle: FocusHandle, _keymap_subscription: Subscription, processed_bindings: Vec, + table_interaction_state: Entity, scroll_handle: UniformListScrollHandle, horizontal_scrollbar: ScrollbarProperties, vertical_scrollbar: ScrollbarProperties, @@ -77,10 +81,7 @@ impl KeymapEditor { this.processed_bindings = key_bindings; }); - cx.on_focus_out(&focus_handle, window, |this, _, window, cx| { - this.hide_scrollbars(window, cx); - }) - .detach(); + let table_interaction_state = TableInteractionState::new(window, cx); let scroll_handle = UniformListScrollHandle::new(); let vertical_scrollbar = ScrollbarProperties { @@ -108,6 +109,7 @@ impl KeymapEditor { scroll_handle, horizontal_scrollbar, vertical_scrollbar, + table_interaction_state, }; this.update_scrollbar_visibility(cx); @@ -392,10 +394,8 @@ impl Render for KeymapEditor { }; let row_count = self.processed_bindings.len(); - let table = Table::new(row_count); let theme = cx.theme(); - let headers = ["Command", "Keystrokes", "Context"].map(Into::into); div() .size_full() @@ -412,108 +412,121 @@ impl Render for KeymapEditor { } })) .child( - table - .h_full() - .v_flex() - .child(table.render_header(headers, cx)) - .child( - div() - .flex_grow() - .w_full() - .relative() - .overflow_hidden() - .child( - uniform_list( - "keybindings", - row_count, - cx.processor(move |this, range, _, cx| { - range - .map(|index| { - let binding = &this.processed_bindings[index]; - let row = [ - binding.action.clone(), - binding.keystroke_text.clone(), - binding.context.clone(), - // TODO: Add a source field - // string_cell(keybinding.source().to_string()), - ] - .map(string_cell); - - table.render_row(index, row, cx) - }) - .collect() - }), - ) - .size_full() - .flex_grow() - .track_scroll(self.scroll_handle.clone()) - .with_sizing_behavior(ListSizingBehavior::Auto) - .with_horizontal_sizing_behavior( - ListHorizontalSizingBehavior::Unconstrained, - ), - ) - .when(self.vertical_scrollbar.show_track, |this| { - this.child( - v_flex() - .h_full() - .flex_none() - .w(scroll_track_size) - .bg(cx.theme().colors().background) - .child( - div() - .size_full() - .flex_1() - .border_l_1() - .border_color(cx.theme().colors().border), - ), + Table::uniform_list( + "keymap-editor-table", + row_count, + cx.processor(|this, range: Range, window, cx| { + range + .map(|index| { + let binding = &this.processed_bindings[index]; + let row = [ + binding.action.clone(), + binding.keystroke_text.clone(), + binding.context.clone(), + // TODO: Add a source field + // binding.source.clone(), + ]; + + // fixme: pass through callback as a row_cx param + let striped = false; + + crate::ui_components::table::render_row( + index, row, row_count, striped, cx, ) }) - .when(self.vertical_scrollbar.show_scrollbar, |this| { - this.child(self.render_vertical_scrollbar(cx)) - }), - ) - .when(self.horizontal_scrollbar.show_track, |this| { - this.child( - h_flex() - .w_full() - .h(scroll_track_size) - .flex_none() - .relative() - .child( - div() - .w_full() - .flex_1() - // for some reason the horizontal scrollbar is 1px - // taller than the vertical scrollbar?? - .h(scroll_track_size - px(1.)) - .bg(cx.theme().colors().background) - .border_t_1() - .border_color(cx.theme().colors().border), - ) - .when(self.vertical_scrollbar.show_track, |this| { - this.child( - div() - .flex_none() - // -1px prevents a missing pixel between the two container borders - .w(scroll_track_size - px(1.)) - .h_full(), - ) - .child( - // HACK: Fill the missing 1px 🥲 - div() - .absolute() - .right(scroll_track_size - px(1.)) - .bottom(scroll_track_size - px(1.)) - .size_px() - .bg(cx.theme().colors().border), - ) - }), - ) - }) - .when(self.horizontal_scrollbar.show_scrollbar, |this| { - this.child(self.render_horizontal_scrollbar(h_scroll_offset, cx)) + .collect() }), + ) + .header(["Command", "Keystrokes", "Context"]) + .interactable(&self.table_interaction_state), ) + // .child( + // table + // .h_full() + // .v_flex() + // .child(table.render_header(headers, cx)) + // .child( + // div() + // .flex_grow() + // .w_full() + // .relative() + // .overflow_hidden() + // .child( + // uniform_list( + // "keybindings", + // row_count, + // cx.processor(move |this, range, _, cx| {}), + // ) + // .size_full() + // .flex_grow() + // .track_scroll(self.scroll_handle.clone()) + // .with_sizing_behavior(ListSizingBehavior::Auto) + // .with_horizontal_sizing_behavior( + // ListHorizontalSizingBehavior::Unconstrained, + // ), + // ) + // .when(self.vertical_scrollbar.show_track, |this| { + // this.child( + // v_flex() + // .h_full() + // .flex_none() + // .w(scroll_track_size) + // .bg(cx.theme().colors().background) + // .child( + // div() + // .size_full() + // .flex_1() + // .border_l_1() + // .border_color(cx.theme().colors().border), + // ), + // ) + // }) + // .when(self.vertical_scrollbar.show_scrollbar, |this| { + // this.child(self.render_vertical_scrollbar(cx)) + // }), + // ) + // .when(self.horizontal_scrollbar.show_track, |this| { + // this.child( + // h_flex() + // .w_full() + // .h(scroll_track_size) + // .flex_none() + // .relative() + // .child( + // div() + // .w_full() + // .flex_1() + // // for some reason the horizontal scrollbar is 1px + // // taller than the vertical scrollbar?? + // .h(scroll_track_size - px(1.)) + // .bg(cx.theme().colors().background) + // .border_t_1() + // .border_color(cx.theme().colors().border), + // ) + // .when(self.vertical_scrollbar.show_track, |this| { + // this.child( + // div() + // .flex_none() + // // -1px prevents a missing pixel between the two container borders + // .w(scroll_track_size - px(1.)) + // .h_full(), + // ) + // .child( + // // HACK: Fill the missing 1px 🥲 + // div() + // .absolute() + // .right(scroll_track_size - px(1.)) + // .bottom(scroll_track_size - px(1.)) + // .size_px() + // .bg(cx.theme().colors().border), + // ) + // }), + // ) + // }) + // .when(self.horizontal_scrollbar.show_scrollbar, |this| { + // this.child(self.render_horizontal_scrollbar(h_scroll_offset, cx)) + // }), + // ) } } diff --git a/crates/settings_ui/src/ui_components/table.rs b/crates/settings_ui/src/ui_components/table.rs index 96a8aa41a7fdda1ec956d1bda254bfea54e33434..1de8379e087e8ebe7c1e44e496e677e24d673052 100644 --- a/crates/settings_ui/src/ui_components/table.rs +++ b/crates/settings_ui/src/ui_components/table.rs @@ -1,12 +1,11 @@ use std::ops::Range; -use db::smol::stream::iter; -use gpui::{Entity, FontWeight, Length, uniform_list}; +use gpui::{AppContext as _, Entity, FocusHandle, FontWeight, Length, WeakEntity, uniform_list}; use ui::{ ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component, - ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, IntoElement, - ParentElement, RegisterComponent, RenderOnce, Styled, StyledTypography, Window, div, - example_group_with_title, px, single_example, v_flex, + ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, + InteractiveElement as _, IntoElement, ParentElement, RegisterComponent, RenderOnce, Styled, + StyledTypography, Window, div, example_group_with_title, px, single_example, v_flex, }; struct UniformListData { @@ -36,6 +35,31 @@ impl TableContents { } } +pub struct TableInteractionState { + pub focus_handle: FocusHandle, +} + +impl TableInteractionState { + pub fn new(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| { + let focus_handle = cx.focus_handle(); + + // cx.on_focus_out(&focus_handle, window, |this, _, window, cx| { + // this.hide_scrollbars(window, cx); + // }) + // .detach(); + + Self { focus_handle } + }) + } +} + +impl TableInteractionState { + pub fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut App) { + todo!() + } +} + /// A table component #[derive(RegisterComponent, IntoElement)] pub struct Table { @@ -43,6 +67,7 @@ pub struct Table { width: Length, headers: Option<[AnyElement; COLS]>, rows: TableContents, + interaction_state: Option>, } impl Table { @@ -60,10 +85,10 @@ impl Table { row_count: row_count, render_item_fn: Box::new(render_item_fn), }), + interaction_state: None, } } - /// Create a new table with a column count equal to the /// number of headers provided. pub fn new() -> Self { Table { @@ -71,6 +96,7 @@ impl Table { width: Length::Auto, headers: None, rows: TableContents::Vec(Vec::new()), + interaction_state: None, } } @@ -86,6 +112,11 @@ impl Table { self } + pub fn interactable(mut self, interaction_state: &Entity) -> Self { + self.interaction_state = Some(interaction_state.downgrade()); + self + } + pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self { self.headers = Some(headers.map(IntoElement::into_any_element)); self @@ -185,6 +216,12 @@ impl RenderOnce for Table { .when_some(self.headers.take(), |this, headers| { this.child(render_header(headers, cx)) }) + .when_some( + self.interaction_state.and_then(|state| state.upgrade()), + |this, interaction_state| { + this.track_focus(&interaction_state.read(cx).focus_handle) + }, + ) .map(|div| match self.rows { TableContents::Vec(items) => div.children( items