feat: apply declarative render rule across components
This commit is contained in:
+151
-148
@@ -518,7 +518,7 @@ where
|
|||||||
|
|
||||||
let wrapper_bounds: Rc<RefCell<Option<Bounds<Pixels>>>> = Rc::new(RefCell::new(None));
|
let wrapper_bounds: Rc<RefCell<Option<Bounds<Pixels>>>> = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
let mut wrapper = div()
|
div()
|
||||||
.id(id.clone())
|
.id(id.clone())
|
||||||
.key_context(COMBOBOX_CONTEXT)
|
.key_context(COMBOBOX_CONTEXT)
|
||||||
.relative()
|
.relative()
|
||||||
@@ -575,159 +575,162 @@ where
|
|||||||
.right(metrics.dense_padding_x)
|
.right(metrics.dense_padding_x)
|
||||||
.top((metrics.input_height + BORDER_WIDTH * 2.0 - icon_size) / 2.)
|
.top((metrics.input_height + BORDER_WIDTH * 2.0 - icon_size) / 2.)
|
||||||
.child(chevron),
|
.child(chevron),
|
||||||
);
|
)
|
||||||
|
.when(open, |wrapper| {
|
||||||
|
let rows: Vec<gpui::AnyElement> = self
|
||||||
|
.visible
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(row, option_ix)| {
|
||||||
|
let (value, label) = &self.options[*option_ix];
|
||||||
|
let highlighted = self.highlight == Some(row);
|
||||||
|
let checked = self.selected.as_ref() == Some(value);
|
||||||
|
let state = state.clone();
|
||||||
|
let row_ix = row;
|
||||||
|
|
||||||
if open {
|
div()
|
||||||
let rows: Vec<gpui::AnyElement> = self
|
.id(("option", row))
|
||||||
.visible
|
.debug_selector({
|
||||||
.iter()
|
let id = id.clone();
|
||||||
.enumerate()
|
move || format!("{id}-option-{row}")
|
||||||
.map(|(row, option_ix)| {
|
})
|
||||||
let (value, label) = &self.options[*option_ix];
|
.flex()
|
||||||
let highlighted = self.highlight == Some(row);
|
.items_center()
|
||||||
let checked = self.selected.as_ref() == Some(value);
|
.gap_2()
|
||||||
let state = state.clone();
|
.px_2()
|
||||||
let row_ix = row;
|
.py_1()
|
||||||
|
.rounded(radius.sm)
|
||||||
div()
|
.when(highlighted, |row| row.bg(colors.muted))
|
||||||
.id(("option", row))
|
.when(!highlighted, |row| row.hover(|row| row.bg(colors.muted)))
|
||||||
.debug_selector({
|
.on_mouse_move({
|
||||||
let id = id.clone();
|
let state = state.clone();
|
||||||
move || format!("{id}-option-{row}")
|
move |_, _window, cx| {
|
||||||
})
|
if state.read(cx).highlight != Some(row_ix) {
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.rounded(radius.sm)
|
|
||||||
.when(highlighted, |row| row.bg(colors.muted))
|
|
||||||
.when(!highlighted, |row| row.hover(|row| row.bg(colors.muted)))
|
|
||||||
.on_mouse_move({
|
|
||||||
let state = state.clone();
|
|
||||||
move |_, _window, cx| {
|
|
||||||
if state.read(cx).highlight != Some(row_ix) {
|
|
||||||
state.update(cx, |state, cx| {
|
|
||||||
state.highlight = Some(row_ix);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_click({
|
|
||||||
let state = state.clone();
|
|
||||||
move |_, window, cx| {
|
|
||||||
state.update(cx, |state, cx| state.choose_row(row_ix, window, cx));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex_1()
|
|
||||||
.min_w_0()
|
|
||||||
.overflow_hidden()
|
|
||||||
.whitespace_nowrap()
|
|
||||||
.text_ellipsis()
|
|
||||||
.child(label.clone()),
|
|
||||||
)
|
|
||||||
.when(checked, |row| {
|
|
||||||
row.child(
|
|
||||||
icon(IconRole::Check)
|
|
||||||
.size(icon_size)
|
|
||||||
.color(colors.foreground),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let rows: gpui::AnyElement = if rows.is_empty() {
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.text_size(metrics.caption_size)
|
|
||||||
.text_color(colors.muted_foreground)
|
|
||||||
.child("No matches")
|
|
||||||
.into_any_element()
|
|
||||||
} else {
|
|
||||||
let scroll = window
|
|
||||||
.use_keyed_state((id.clone(), "rows-scroll"), cx, |_, _| ScrollHandle::new());
|
|
||||||
|
|
||||||
ScrollArea::new((id.clone(), "rows"))
|
|
||||||
.handle(scroll.read(cx).clone())
|
|
||||||
.axes(crate::scroll_area::ScrollAxes::Vertical)
|
|
||||||
.max_h(POPUP_MAX_HEIGHT)
|
|
||||||
.children(rows)
|
|
||||||
.into_any_element()
|
|
||||||
};
|
|
||||||
|
|
||||||
let panel_bounds: Rc<RefCell<Option<Bounds<Pixels>>>> = Rc::new(RefCell::new(None));
|
|
||||||
let popup_width = wrapper_bounds
|
|
||||||
.borrow()
|
|
||||||
.map(|bounds| bounds.size.width)
|
|
||||||
.unwrap_or_else(|| window.rem_size() * 12.0);
|
|
||||||
|
|
||||||
let panel = v_flex()
|
|
||||||
.id((id.clone(), "panel"))
|
|
||||||
.debug_selector({
|
|
||||||
let id = id.clone();
|
|
||||||
move || format!("{id}-panel")
|
|
||||||
})
|
|
||||||
.w(popup_width)
|
|
||||||
.p_1()
|
|
||||||
.bg(colors.surface)
|
|
||||||
.text_color(colors.foreground)
|
|
||||||
.rounded(radius.overlay)
|
|
||||||
.border_1()
|
|
||||||
.border_color(panel_border)
|
|
||||||
.shadow(shadows)
|
|
||||||
.overflow_hidden()
|
|
||||||
.child(rows)
|
|
||||||
.child(
|
|
||||||
canvas(
|
|
||||||
{
|
|
||||||
let cell = panel_bounds.clone();
|
|
||||||
move |bounds, _window, _cx| *cell.borrow_mut() = Some(bounds)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
let state = state.clone();
|
|
||||||
let panel_bounds = panel_bounds.clone();
|
|
||||||
let wrapper_bounds = wrapper_bounds.clone();
|
|
||||||
move |_, _, window, _cx| {
|
|
||||||
window.on_mouse_event({
|
|
||||||
let state = state.clone();
|
|
||||||
let panel_bounds = panel_bounds.clone();
|
|
||||||
let wrapper_bounds = wrapper_bounds.clone();
|
|
||||||
move |event: &MouseDownEvent, phase, _window, cx| {
|
|
||||||
if phase != gpui::DispatchPhase::Bubble {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let on_panel = panel_bounds
|
|
||||||
.borrow()
|
|
||||||
.is_some_and(|bounds| bounds.contains(&event.position));
|
|
||||||
let on_wrapper = wrapper_bounds
|
|
||||||
.borrow()
|
|
||||||
.is_some_and(|bounds| bounds.contains(&event.position));
|
|
||||||
if on_panel || on_wrapper {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.update(cx, |state, cx| {
|
state.update(cx, |state, cx| {
|
||||||
state.highlight = None;
|
state.highlight = Some(row_ix);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
},
|
.on_click({
|
||||||
)
|
let state = state.clone();
|
||||||
.absolute()
|
move |_, window, cx| {
|
||||||
.inset_0(),
|
state.update(cx, |state, cx| {
|
||||||
);
|
state.choose_row(row_ix, window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex_1()
|
||||||
|
.min_w_0()
|
||||||
|
.overflow_hidden()
|
||||||
|
.whitespace_nowrap()
|
||||||
|
.text_ellipsis()
|
||||||
|
.child(label.clone()),
|
||||||
|
)
|
||||||
|
.when(checked, |row| {
|
||||||
|
row.child(
|
||||||
|
icon(IconRole::Check)
|
||||||
|
.size(icon_size)
|
||||||
|
.color(colors.foreground),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
wrapper = wrapper.child(deferred(
|
let rows: gpui::AnyElement = if rows.is_empty() {
|
||||||
anchored().offset(point(px(0.), spacing_unit)).child(panel),
|
div()
|
||||||
));
|
.px_2()
|
||||||
}
|
.py_1()
|
||||||
|
.text_size(metrics.caption_size)
|
||||||
|
.text_color(colors.muted_foreground)
|
||||||
|
.child("No matches")
|
||||||
|
.into_any_element()
|
||||||
|
} else {
|
||||||
|
let scroll = window.use_keyed_state((id.clone(), "rows-scroll"), cx, |_, _| {
|
||||||
|
ScrollHandle::new()
|
||||||
|
});
|
||||||
|
|
||||||
wrapper
|
ScrollArea::new((id.clone(), "rows"))
|
||||||
|
.handle(scroll.read(cx).clone())
|
||||||
|
.axes(crate::scroll_area::ScrollAxes::Vertical)
|
||||||
|
.max_h(POPUP_MAX_HEIGHT)
|
||||||
|
.children(rows)
|
||||||
|
.into_any_element()
|
||||||
|
};
|
||||||
|
|
||||||
|
let panel_bounds: Rc<RefCell<Option<Bounds<Pixels>>>> = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
|
let popup_width = wrapper_bounds
|
||||||
|
.borrow()
|
||||||
|
.map(|bounds| bounds.size.width)
|
||||||
|
.unwrap_or_else(|| window.rem_size() * 12.0);
|
||||||
|
|
||||||
|
let panel = v_flex()
|
||||||
|
.id((id.clone(), "panel"))
|
||||||
|
.debug_selector({
|
||||||
|
let id = id.clone();
|
||||||
|
move || format!("{id}-panel")
|
||||||
|
})
|
||||||
|
.w(popup_width)
|
||||||
|
.p_1()
|
||||||
|
.bg(colors.surface)
|
||||||
|
.text_color(colors.foreground)
|
||||||
|
.rounded(radius.overlay)
|
||||||
|
.border_1()
|
||||||
|
.border_color(panel_border)
|
||||||
|
.shadow(shadows)
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(rows)
|
||||||
|
.child(
|
||||||
|
canvas(
|
||||||
|
{
|
||||||
|
let cell = panel_bounds.clone();
|
||||||
|
move |bounds, _window, _cx| *cell.borrow_mut() = Some(bounds)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let state = state.clone();
|
||||||
|
let panel_bounds = panel_bounds.clone();
|
||||||
|
let wrapper_bounds = wrapper_bounds.clone();
|
||||||
|
move |_, _, window, _cx| {
|
||||||
|
window.on_mouse_event({
|
||||||
|
let state = state.clone();
|
||||||
|
let panel_bounds = panel_bounds.clone();
|
||||||
|
let wrapper_bounds = wrapper_bounds.clone();
|
||||||
|
move |event: &MouseDownEvent, phase, _window, cx| {
|
||||||
|
if phase != gpui::DispatchPhase::Bubble {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let on_panel =
|
||||||
|
panel_bounds.borrow().is_some_and(|bounds| {
|
||||||
|
bounds.contains(&event.position)
|
||||||
|
});
|
||||||
|
let on_wrapper =
|
||||||
|
wrapper_bounds.borrow().is_some_and(|bounds| {
|
||||||
|
bounds.contains(&event.position)
|
||||||
|
});
|
||||||
|
if on_panel || on_wrapper {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.update(cx, |state, cx| {
|
||||||
|
state.highlight = None;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.child(deferred(
|
||||||
|
anchored().offset(point(px(0.), spacing_unit)).child(panel),
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,7 +366,15 @@ impl RenderOnce for DropdownMenu {
|
|||||||
trigger
|
trigger
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut wrapper = div()
|
let items = if self.open {
|
||||||
|
self.items
|
||||||
|
.map(|build| Rc::from(build(window, cx)))
|
||||||
|
.unwrap_or_else(|| Rc::from([]))
|
||||||
|
} else {
|
||||||
|
Rc::from([])
|
||||||
|
};
|
||||||
|
|
||||||
|
div()
|
||||||
.relative()
|
.relative()
|
||||||
.child(
|
.child(
|
||||||
canvas(
|
canvas(
|
||||||
@@ -421,34 +429,24 @@ impl RenderOnce for DropdownMenu {
|
|||||||
.absolute()
|
.absolute()
|
||||||
.inset_0(),
|
.inset_0(),
|
||||||
)
|
)
|
||||||
.child(trigger);
|
.child(trigger)
|
||||||
|
.when_some(
|
||||||
let items = if self.open {
|
anchored_menu_panel(
|
||||||
self.items
|
window,
|
||||||
.map(|build| Rc::from(build(window, cx)))
|
cx,
|
||||||
.unwrap_or_else(|| Rc::from([]))
|
&self.id,
|
||||||
} else {
|
self.open,
|
||||||
Rc::from([])
|
items,
|
||||||
};
|
None,
|
||||||
|
*keyboard_open.read(cx),
|
||||||
if let Some(panel) = anchored_menu_panel(
|
Some(on_open),
|
||||||
window,
|
label,
|
||||||
cx,
|
None,
|
||||||
&self.id,
|
&trigger_bounds,
|
||||||
self.open,
|
&panel_chrome,
|
||||||
items,
|
enter_duration,
|
||||||
None,
|
),
|
||||||
*keyboard_open.read(cx),
|
|wrapper, panel| wrapper.child(panel),
|
||||||
Some(on_open),
|
)
|
||||||
label,
|
|
||||||
None,
|
|
||||||
&trigger_bounds,
|
|
||||||
&panel_chrome,
|
|
||||||
enter_duration,
|
|
||||||
) {
|
|
||||||
wrapper = wrapper.child(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-32
@@ -1,7 +1,7 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, DispatchPhase, ElementId,
|
Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, DispatchPhase, ElementId,
|
||||||
Entity, InteractiveElement as _, IntoElement, KeyDownEvent, MouseButton, MouseDownEvent,
|
Entity, InteractiveElement as _, IntoElement, KeyDownEvent, MouseButton, MouseDownEvent,
|
||||||
@@ -127,6 +127,9 @@ impl MenuItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MenuItem is a data builder, not an element, adopt the combinators explicitly.
|
||||||
|
impl FluentBuilder for MenuItem {}
|
||||||
|
|
||||||
/// Menu chrome styles.
|
/// Menu chrome styles.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct MenuChrome {
|
pub(crate) struct MenuChrome {
|
||||||
@@ -288,7 +291,16 @@ impl RenderOnce for Menu {
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| SharedString::from(self.id.to_string()));
|
.unwrap_or_else(|| SharedString::from(self.id.to_string()));
|
||||||
|
|
||||||
let mut wrapper = div()
|
let items = if self.open {
|
||||||
|
match self.items {
|
||||||
|
Some(build) => Rc::from(build(window, cx)),
|
||||||
|
None => Rc::from([]),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Rc::from([])
|
||||||
|
};
|
||||||
|
|
||||||
|
div()
|
||||||
.relative()
|
.relative()
|
||||||
.child(
|
.child(
|
||||||
canvas(
|
canvas(
|
||||||
@@ -335,36 +347,25 @@ impl RenderOnce for Menu {
|
|||||||
)
|
)
|
||||||
.when_some(self.trigger, |wrapper, trigger| {
|
.when_some(self.trigger, |wrapper, trigger| {
|
||||||
wrapper.child(trigger(window, cx))
|
wrapper.child(trigger(window, cx))
|
||||||
});
|
})
|
||||||
|
.when_some(
|
||||||
let items = if self.open {
|
anchored_menu_panel(
|
||||||
match self.items {
|
window,
|
||||||
Some(build) => Rc::from(build(window, cx)),
|
cx,
|
||||||
None => Rc::from([]),
|
&self.id,
|
||||||
}
|
self.open,
|
||||||
} else {
|
items,
|
||||||
Rc::from([])
|
self.initial_selection,
|
||||||
};
|
false,
|
||||||
|
self.on_change,
|
||||||
if let Some(panel) = anchored_menu_panel(
|
fallback_label,
|
||||||
window,
|
self.open_at,
|
||||||
cx,
|
&trigger_bounds,
|
||||||
&self.id,
|
&chrome,
|
||||||
self.open,
|
enter_duration,
|
||||||
items,
|
),
|
||||||
self.initial_selection,
|
|wrapper, panel| wrapper.child(panel),
|
||||||
false,
|
)
|
||||||
self.on_change,
|
|
||||||
fallback_label,
|
|
||||||
self.open_at,
|
|
||||||
&trigger_bounds,
|
|
||||||
&chrome,
|
|
||||||
enter_duration,
|
|
||||||
) {
|
|
||||||
wrapper = wrapper.child(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+64
-67
@@ -114,7 +114,7 @@ impl RenderOnce for Popover {
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| SharedString::from(self.id.to_string()));
|
.unwrap_or_else(|| SharedString::from(self.id.to_string()));
|
||||||
|
|
||||||
let mut wrapper = div()
|
div()
|
||||||
.relative()
|
.relative()
|
||||||
.child(
|
.child(
|
||||||
canvas(
|
canvas(
|
||||||
@@ -161,80 +161,77 @@ impl RenderOnce for Popover {
|
|||||||
)
|
)
|
||||||
.when_some(self.trigger, |wrapper, trigger| {
|
.when_some(self.trigger, |wrapper, trigger| {
|
||||||
wrapper.child(trigger(window, cx))
|
wrapper.child(trigger(window, cx))
|
||||||
});
|
})
|
||||||
|
.when(self.open, |wrapper| {
|
||||||
|
let on_change = self.on_change;
|
||||||
|
let label = fallback_label.clone();
|
||||||
|
let focus = gpui_native_utils::focus_handle(&self.id, window, cx);
|
||||||
|
let content = self.content.map(|content| content(window, cx));
|
||||||
|
|
||||||
if self.open {
|
let panel = v_flex()
|
||||||
let on_change = self.on_change;
|
.id("popover-panel")
|
||||||
let label = fallback_label.clone();
|
.debug_selector(|| "popover-panel".to_string())
|
||||||
let focus = gpui_native_utils::focus_handle(&self.id, window, cx);
|
.occlude()
|
||||||
let content = self.content.map(|content| content(window, cx));
|
.track_focus(&focus)
|
||||||
|
.role(Role::Dialog)
|
||||||
let panel = v_flex()
|
.aria_label(label)
|
||||||
.id("popover-panel")
|
.min_w_32()
|
||||||
.debug_selector(|| "popover-panel".to_string())
|
.bg(colors.surface)
|
||||||
.occlude()
|
.text_color(colors.surface_foreground)
|
||||||
.track_focus(&focus)
|
.rounded(radius.overlay)
|
||||||
.role(Role::Dialog)
|
.border_1()
|
||||||
.aria_label(label)
|
.border_color(panel_border)
|
||||||
.min_w_32()
|
.shadow(shadows)
|
||||||
.bg(colors.surface)
|
.overflow_hidden()
|
||||||
.text_color(colors.surface_foreground)
|
.on_mouse_down_out({
|
||||||
.rounded(radius.overlay)
|
let on_change = on_change.clone();
|
||||||
.border_1()
|
let trigger_bounds = trigger_bounds.clone();
|
||||||
.border_color(panel_border)
|
move |event, window, cx| {
|
||||||
.shadow(shadows)
|
if trigger_bounds
|
||||||
.overflow_hidden()
|
.borrow()
|
||||||
.on_mouse_down_out({
|
.is_some_and(|bounds| bounds.contains(&event.position))
|
||||||
let on_change = on_change.clone();
|
{
|
||||||
let trigger_bounds = trigger_bounds.clone();
|
return;
|
||||||
move |event, window, cx| {
|
}
|
||||||
if trigger_bounds
|
|
||||||
.borrow()
|
|
||||||
.is_some_and(|bounds| bounds.contains(&event.position))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(on_change) = &on_change {
|
|
||||||
on_change(false, window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_key_down({
|
|
||||||
let on_change = on_change.clone();
|
|
||||||
move |event: &KeyDownEvent, window, cx| {
|
|
||||||
if event.keystroke.key == "escape" {
|
|
||||||
if let Some(on_change) = &on_change {
|
if let Some(on_change) = &on_change {
|
||||||
on_change(false, window, cx);
|
on_change(false, window, cx);
|
||||||
}
|
}
|
||||||
cx.stop_propagation();
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.on_key_down({
|
||||||
.when_some(content, |panel, content| panel.child(content))
|
let on_change = on_change.clone();
|
||||||
.child(
|
move |event: &KeyDownEvent, window, cx| {
|
||||||
canvas(|_, _, _| {}, {
|
if event.keystroke.key == "escape" {
|
||||||
let focus = focus;
|
if let Some(on_change) = &on_change {
|
||||||
move |_, _, window, cx| {
|
on_change(false, window, cx);
|
||||||
if !focus.contains_focused(window, cx) {
|
}
|
||||||
window.focus(&focus, cx);
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.absolute()
|
.when_some(content, |panel, content| panel.child(content))
|
||||||
.inset_0(),
|
.child(
|
||||||
);
|
canvas(|_, _, _| {}, {
|
||||||
|
let focus = focus;
|
||||||
|
move |_, _, window, cx| {
|
||||||
|
if !focus.contains_focused(window, cx) {
|
||||||
|
window.focus(&focus, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
|
);
|
||||||
|
|
||||||
wrapper = wrapper.child(deferred(
|
wrapper.child(deferred(
|
||||||
anchored()
|
anchored()
|
||||||
.offset(point(px(0.), anchor_gap))
|
.offset(point(px(0.), anchor_gap))
|
||||||
.child(panel.with_animation(
|
.child(panel.with_animation(
|
||||||
"popover-enter",
|
"popover-enter",
|
||||||
Animation::new(enter_duration).with_easing(ease_out_quint()),
|
Animation::new(enter_duration).with_easing(ease_out_quint()),
|
||||||
|panel, delta| panel.opacity(delta),
|
|panel, delta| panel.opacity(delta),
|
||||||
)),
|
)),
|
||||||
));
|
))
|
||||||
}
|
})
|
||||||
|
|
||||||
wrapper
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ impl RenderOnce for ScrollArea {
|
|||||||
let mut pane_style = self.style.clone();
|
let mut pane_style = self.style.clone();
|
||||||
pane_style.padding = Default::default();
|
pane_style.padding = Default::default();
|
||||||
|
|
||||||
let mut pane = div()
|
let pane = div()
|
||||||
.id(self.id.clone())
|
.id(self.id.clone())
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.when(self.axes != ScrollAxes::Horizontal, |area| {
|
.when(self.axes != ScrollAxes::Horizontal, |area| {
|
||||||
@@ -112,34 +112,31 @@ impl RenderOnce for ScrollArea {
|
|||||||
// Keep wheel events from scrolling an outer scroll area too.
|
// Keep wheel events from scrolling an outer scroll area too.
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
})
|
||||||
.refine_style(&pane_style);
|
.refine_style(&pane_style)
|
||||||
|
.children(self.children);
|
||||||
|
|
||||||
// Extend with children
|
div()
|
||||||
pane.extend(self.children);
|
.relative()
|
||||||
|
.refine_style(&self.style)
|
||||||
|
.child(pane.into_any_element())
|
||||||
|
.when_some(self.handle, |wrapper, handle| {
|
||||||
|
let id = self.id.clone();
|
||||||
|
|
||||||
let mut wrapper = div().relative().refine_style(&self.style);
|
wrapper
|
||||||
wrapper.extend(std::iter::once(pane.into_any_element()));
|
.when(self.axes != ScrollAxes::Horizontal, |wrapper| {
|
||||||
|
wrapper.child(
|
||||||
if let Some(handle) = self.handle {
|
Scrollbar::vertical((id.clone(), "v-scrollbar"), handle.clone())
|
||||||
let id = self.id.clone();
|
.visibility(self.visibility)
|
||||||
|
.into_any_element(),
|
||||||
if self.axes != ScrollAxes::Horizontal {
|
)
|
||||||
wrapper.extend(std::iter::once(
|
})
|
||||||
Scrollbar::vertical((id.clone(), "v-scrollbar"), handle.clone())
|
.when(self.axes != ScrollAxes::Vertical, |wrapper| {
|
||||||
.visibility(self.visibility)
|
wrapper.child(
|
||||||
.into_any_element(),
|
Scrollbar::horizontal((id, "h-scrollbar"), handle)
|
||||||
));
|
.visibility(self.visibility)
|
||||||
}
|
.into_any_element(),
|
||||||
|
)
|
||||||
if self.axes != ScrollAxes::Vertical {
|
})
|
||||||
wrapper.extend(std::iter::once(
|
})
|
||||||
Scrollbar::horizontal((id, "h-scrollbar"), handle)
|
|
||||||
.visibility(self.visibility)
|
|
||||||
.into_any_element(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,11 +274,10 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut row = MenuItem::new(option.label.clone());
|
MenuItem::new(option.label.clone())
|
||||||
if let Some(icon) = option.icon.clone() {
|
.when_some(option.icon.clone(), |row, icon| row.icon(icon))
|
||||||
row = row.icon(icon);
|
.checked(is_selected)
|
||||||
}
|
.handler(handler)
|
||||||
row.checked(is_selected).handler(handler)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|||||||
+65
-73
@@ -1,6 +1,6 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Div, ElementId, InteractiveElement as _, IntoElement, KeyDownEvent,
|
AnyElement, App, Div, ElementId, InteractiveElement as _, IntoElement, KeyDownEvent,
|
||||||
ParentElement as _, RenderOnce, Role, SharedString, Stateful, StatefulInteractiveElement as _,
|
ParentElement as _, RenderOnce, Role, SharedString, Stateful, StatefulInteractiveElement as _,
|
||||||
@@ -97,6 +97,9 @@ impl Tab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tab is a data builder, not an element, adopt the combinators explicitly.
|
||||||
|
impl FluentBuilder for Tab {}
|
||||||
|
|
||||||
/// A tab strip.
|
/// A tab strip.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Tabs {
|
pub struct Tabs {
|
||||||
@@ -215,7 +218,7 @@ impl RenderOnce for Tabs {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut strip_root = div()
|
div()
|
||||||
.id(self.id.clone())
|
.id(self.id.clone())
|
||||||
.flex()
|
.flex()
|
||||||
.text_size(theme.sizes.medium.text_size)
|
.text_size(theme.sizes.medium.text_size)
|
||||||
@@ -243,17 +246,14 @@ impl RenderOnce for Tabs {
|
|||||||
}
|
}
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.map(|root| match self.variant {
|
||||||
strip_root = match self.variant {
|
TabsVariant::Underline => {
|
||||||
TabsVariant::Underline => strip_root
|
root.gap_1().border_b_1().border_color(theme.colors.border)
|
||||||
.gap_1()
|
}
|
||||||
.border_b_1()
|
TabsVariant::Pill => root.items_center().gap_1(),
|
||||||
.border_color(theme.colors.border),
|
})
|
||||||
TabsVariant::Pill => strip_root.items_center().gap_1(),
|
.children(strip)
|
||||||
};
|
|
||||||
|
|
||||||
strip_root.children(strip)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ fn render_tab(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut strip_tab = div()
|
let strip_tab = div()
|
||||||
.id((tab.key.clone(), "tab"))
|
.id((tab.key.clone(), "tab"))
|
||||||
.debug_selector({
|
.debug_selector({
|
||||||
let tabs_id = tabs_id.clone();
|
let tabs_id = tabs_id.clone();
|
||||||
@@ -310,67 +310,59 @@ fn render_tab(
|
|||||||
.text_color(label_color)
|
.text_color(label_color)
|
||||||
.when(!tab.disabled && !selected, |strip_tab| {
|
.when(!tab.disabled && !selected, |strip_tab| {
|
||||||
strip_tab.hover(|strip_tab| strip_tab.bg(theme.colors.muted))
|
strip_tab.hover(|strip_tab| strip_tab.bg(theme.colors.muted))
|
||||||
});
|
})
|
||||||
|
.map(|strip_tab| match variant {
|
||||||
strip_tab = match variant {
|
TabsVariant::Underline => strip_tab
|
||||||
TabsVariant::Underline => strip_tab
|
.px_2()
|
||||||
.px_2()
|
.py_1()
|
||||||
.py_1()
|
.rounded_tl(theme.radius.sm)
|
||||||
.rounded_tl(theme.radius.sm)
|
.rounded_tr(theme.radius.sm)
|
||||||
.rounded_tr(theme.radius.sm)
|
.border_b_2()
|
||||||
.border_b_2()
|
.border_color(gpui::transparent_black())
|
||||||
.border_color(gpui::transparent_black())
|
.when(selected, |strip_tab| {
|
||||||
.when(selected, |strip_tab| {
|
strip_tab.border_color(theme.ramps.primary.base)
|
||||||
strip_tab.border_color(theme.ramps.primary.base)
|
}),
|
||||||
}),
|
TabsVariant::Pill => strip_tab
|
||||||
TabsVariant::Pill => strip_tab
|
.h(small.control_height + metrics.plate_inset * 2.0)
|
||||||
.h(small.control_height + metrics.plate_inset * 2.0)
|
.px(metrics.dense_padding_x)
|
||||||
.px(metrics.dense_padding_x)
|
.pr(metrics.plate_inset)
|
||||||
.pr(metrics.plate_inset)
|
.rounded(theme.radius.control)
|
||||||
.rounded(theme.radius.control)
|
.when(selected, |strip_tab| {
|
||||||
.when(selected, |strip_tab| {
|
strip_tab
|
||||||
strip_tab
|
.bg(theme.colors.muted)
|
||||||
.bg(theme.colors.muted)
|
.hover(|strip_tab| strip_tab.bg(theme.colors.muted_hover))
|
||||||
.hover(|strip_tab| strip_tab.bg(theme.colors.muted_hover))
|
}),
|
||||||
}),
|
})
|
||||||
};
|
.when_some(tab.lead.take(), |strip_tab, lead| strip_tab.child(lead))
|
||||||
|
.child(tab.label.clone())
|
||||||
if let Some(lead) = tab.lead.take() {
|
.when(variant == TabsVariant::Pill, |strip_tab| {
|
||||||
strip_tab = strip_tab.child(lead);
|
strip_tab.child(div().flex_none().w(metrics.padding_x * 1.5))
|
||||||
}
|
})
|
||||||
|
.when(tab.busy && !tab.disabled, |strip_tab| {
|
||||||
strip_tab = strip_tab.child(tab.label.clone());
|
strip_tab.child(
|
||||||
|
Spinner::new((tab.key.clone(), "tab-busy"))
|
||||||
if variant == TabsVariant::Pill {
|
.small()
|
||||||
strip_tab = strip_tab.child(div().flex_none().w(metrics.padding_x * 1.5));
|
.accessibility_label("Loading"),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
if tab.busy && !tab.disabled {
|
.when(
|
||||||
strip_tab = strip_tab.child(
|
tab.closeable && on_close.is_some() && !tab.disabled,
|
||||||
Spinner::new((tab.key.clone(), "tab-busy"))
|
|strip_tab| strip_tab.child(render_tab_close(tabs_id, &tab, index, on_close)),
|
||||||
.small()
|
)
|
||||||
.accessibility_label("Loading"),
|
.when(!tab.disabled, |strip_tab| {
|
||||||
);
|
strip_tab.cursor_pointer().on_click({
|
||||||
}
|
let on_change = on_change.clone();
|
||||||
|
let focus = focus.clone();
|
||||||
if tab.closeable && on_close.is_some() && !tab.disabled {
|
move |_, window, cx| {
|
||||||
strip_tab = strip_tab.child(render_tab_close(tabs_id, &tab, index, on_close));
|
window.focus(&focus, cx);
|
||||||
}
|
if index != selected_index
|
||||||
|
&& let Some(on_change) = &on_change
|
||||||
if !tab.disabled {
|
{
|
||||||
strip_tab = strip_tab.cursor_pointer().on_click({
|
on_change(index, window, cx);
|
||||||
let on_change = on_change.clone();
|
}
|
||||||
let focus = focus.clone();
|
|
||||||
move |_, window, cx| {
|
|
||||||
window.focus(&focus, cx);
|
|
||||||
if index != selected_index
|
|
||||||
&& let Some(on_change) = &on_change
|
|
||||||
{
|
|
||||||
on_change(index, window, cx);
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
match tab.wrap {
|
match tab.wrap {
|
||||||
Some(wrap) => wrap(strip_tab),
|
Some(wrap) => wrap(strip_tab),
|
||||||
|
|||||||
@@ -546,24 +546,25 @@ fn render_row(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.when(options.line_numbers, |row_element| {
|
.when(options.line_numbers, |row_element| {
|
||||||
let mut number = gutter_cell(
|
row_element.child(
|
||||||
cx,
|
gutter_cell(
|
||||||
colors.muted_foreground,
|
cx,
|
||||||
row.new_number
|
colors.muted_foreground,
|
||||||
.or(row.old_number)
|
row.new_number
|
||||||
.map(|number| number.to_string())
|
.or(row.old_number)
|
||||||
.unwrap_or_default(),
|
.map(|number| number.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.w_10()
|
||||||
|
.whitespace_nowrap()
|
||||||
|
.bg(match row.kind {
|
||||||
|
DiffRowKind::Context => wash_gutter(&colors),
|
||||||
|
_ => wash_strong(change_color),
|
||||||
|
})
|
||||||
|
.when(options.indicators == DiffIndicators::Bars, |number| {
|
||||||
|
number.border_l_4().border_color(change_color)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.w_10()
|
|
||||||
.whitespace_nowrap()
|
|
||||||
.bg(match row.kind {
|
|
||||||
DiffRowKind::Context => wash_gutter(&colors),
|
|
||||||
_ => wash_strong(change_color),
|
|
||||||
});
|
|
||||||
if options.indicators == DiffIndicators::Bars {
|
|
||||||
number = number.border_l_4().border_color(change_color);
|
|
||||||
}
|
|
||||||
row_element.child(number)
|
|
||||||
})
|
})
|
||||||
.when(
|
.when(
|
||||||
options.indicators == DiffIndicators::Bars && !options.line_numbers,
|
options.indicators == DiffIndicators::Bars && !options.line_numbers,
|
||||||
|
|||||||
@@ -817,43 +817,43 @@ fn text_row(
|
|||||||
cx: &App,
|
cx: &App,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let mut frame = block_frame(indent, quote, px(0.), bottom, id, cx);
|
|
||||||
|
|
||||||
if let Some(marker) = marker {
|
block_frame(indent, quote, px(0.), bottom, id, cx)
|
||||||
let line_height = theme.sizes.small.text_size * BODY_LINE_HEIGHT;
|
.map(|frame| match marker {
|
||||||
let marker_cell = div()
|
Some(marker) => {
|
||||||
.flex_none()
|
let line_height = theme.sizes.small.text_size * BODY_LINE_HEIGHT;
|
||||||
.w(theme.root_font_size * MARKER_WIDTH)
|
let marker_cell = div()
|
||||||
.h(line_height)
|
.flex_none()
|
||||||
.flex()
|
.w(theme.root_font_size * MARKER_WIDTH)
|
||||||
.items_center()
|
.h(line_height)
|
||||||
.justify_end()
|
.flex()
|
||||||
.pr_1()
|
.items_center()
|
||||||
.child(match marker {
|
.justify_end()
|
||||||
ListMarker::Bullet => div()
|
.pr_1()
|
||||||
.text_color(theme.colors.muted_foreground)
|
.child(match marker {
|
||||||
.child("•")
|
ListMarker::Bullet => div()
|
||||||
.into_any_element(),
|
.text_color(theme.colors.muted_foreground)
|
||||||
ListMarker::Ordered(number) => div()
|
.child("•")
|
||||||
.text_color(theme.colors.muted_foreground)
|
.into_any_element(),
|
||||||
.whitespace_nowrap()
|
ListMarker::Ordered(number) => div()
|
||||||
.child(format!("{number}."))
|
.text_color(theme.colors.muted_foreground)
|
||||||
.into_any_element(),
|
.whitespace_nowrap()
|
||||||
ListMarker::Task(checked) => task_box(checked, cx),
|
.child(format!("{number}."))
|
||||||
});
|
.into_any_element(),
|
||||||
|
ListMarker::Task(checked) => task_box(checked, cx),
|
||||||
|
});
|
||||||
|
|
||||||
frame = frame.child(
|
frame.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_start()
|
.items_start()
|
||||||
.child(marker_cell)
|
.child(marker_cell)
|
||||||
.child(div().flex_1().min_w_0().child(text_child(spans, id, cx))),
|
.child(div().flex_1().min_w_0().child(text_child(spans, id, cx))),
|
||||||
);
|
)
|
||||||
} else {
|
}
|
||||||
frame = frame.child(text_child(spans, id, cx));
|
None => frame.child(text_child(spans, id, cx)),
|
||||||
}
|
})
|
||||||
|
.into_any_element()
|
||||||
frame.into_any_element()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A read-only task item indicator, matching the checkbox component.
|
/// A read-only task item indicator, matching the checkbox component.
|
||||||
@@ -896,7 +896,7 @@ fn code_row(
|
|||||||
let text_size = theme.sizes.small.text_size;
|
let text_size = theme.sizes.small.text_size;
|
||||||
let line_height = text_size * CODE_LINE_HEIGHT;
|
let line_height = text_size * CODE_LINE_HEIGHT;
|
||||||
|
|
||||||
let mut plate = div()
|
let plate = div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
@@ -906,17 +906,14 @@ fn code_row(
|
|||||||
.font_family(theme.mono_font)
|
.font_family(theme.mono_font)
|
||||||
.text_size(text_size)
|
.text_size(text_size)
|
||||||
.line_height(relative(CODE_LINE_HEIGHT))
|
.line_height(relative(CODE_LINE_HEIGHT))
|
||||||
.overflow_hidden();
|
.overflow_hidden()
|
||||||
|
.children(lines.lines.iter().map(|line| {
|
||||||
for line in &lines.lines {
|
|
||||||
plate = plate.child(
|
|
||||||
div()
|
div()
|
||||||
.h(line_height)
|
.h(line_height)
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(code_line(line, theme.colors, theme.ramps)),
|
.child(code_line(line, theme.colors, theme.ramps))
|
||||||
);
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
block_frame(
|
block_frame(
|
||||||
indent,
|
indent,
|
||||||
@@ -961,48 +958,47 @@ fn table_row(
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let row_count = table.rows.len();
|
let row_count = table.rows.len();
|
||||||
|
|
||||||
let mut grid = div()
|
let grid = div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(colors.border)
|
.border_color(colors.border)
|
||||||
.rounded(theme.radius.control)
|
.rounded(theme.radius.control)
|
||||||
.overflow_hidden();
|
.overflow_hidden()
|
||||||
|
.children(table.rows.iter().enumerate().map(|(row_ix, row)| {
|
||||||
for (row_ix, row) in table.rows.iter().enumerate() {
|
div()
|
||||||
let mut row_element = div().w_full().flex();
|
.w_full()
|
||||||
if row_ix + 1 < row_count {
|
.flex()
|
||||||
row_element = row_element.border_b_1().border_color(colors.border);
|
.when(row_ix + 1 < row_count, |row_element| {
|
||||||
}
|
row_element.border_b_1().border_color(colors.border)
|
||||||
if row.header {
|
})
|
||||||
row_element = row_element.bg(colors.muted);
|
.when(row.header, |row_element| row_element.bg(colors.muted))
|
||||||
}
|
.children(
|
||||||
|
row.cells
|
||||||
for (column_ix, cell) in row.cells.iter().enumerate() {
|
.iter()
|
||||||
let cell_id = ElementId::from(SharedString::from(format!(
|
.enumerate()
|
||||||
"{id}-cell-{row_ix}-{column_ix}"
|
.map(|(column_ix, cell)| {
|
||||||
)));
|
let cell_id = ElementId::from(SharedString::from(format!(
|
||||||
row_element = row_element.child(
|
"{id}-cell-{row_ix}-{column_ix}"
|
||||||
div()
|
)));
|
||||||
.flex_1()
|
div()
|
||||||
.min_w_0()
|
.flex_1()
|
||||||
.px_2()
|
.min_w_0()
|
||||||
.py_1()
|
.px_2()
|
||||||
.when(row.header, |cell_element| {
|
.py_1()
|
||||||
cell_element.font_weight(FontWeight::SEMIBOLD)
|
.when(row.header, |cell_element| {
|
||||||
})
|
cell_element.font_weight(FontWeight::SEMIBOLD)
|
||||||
.child(text_child(&cell.spans, &cell_id, cx)),
|
})
|
||||||
);
|
.child(text_child(&cell.spans, &cell_id, cx))
|
||||||
}
|
})
|
||||||
|
// Ragged tables pad missing cells so rows align.
|
||||||
// Ragged tables pad missing cells so rows align.
|
.chain(
|
||||||
for _ in row.cells.len()..width {
|
(row.cells.len()..width)
|
||||||
row_element = row_element.child(div().flex_1().min_w_0().px_2().py_1());
|
.map(|_| div().flex_1().min_w_0().px_2().py_1()),
|
||||||
}
|
),
|
||||||
|
)
|
||||||
grid = grid.child(row_element);
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
block_frame(
|
block_frame(
|
||||||
indent,
|
indent,
|
||||||
|
|||||||
+25
-24
@@ -1044,30 +1044,31 @@ fn render_pane(
|
|||||||
|
|
||||||
tabs.iter()
|
tabs.iter()
|
||||||
.map(|tab| {
|
.map(|tab| {
|
||||||
let mut entry = Tab::new(tab.panel.clone(), tab.title.clone());
|
Tab::new(tab.panel.clone(), tab.title.clone())
|
||||||
if let Some(icon) = tab.icon.clone() {
|
.when_some(tab.icon.clone(), |entry, icon| {
|
||||||
entry = entry.lead(icon.size(metrics.icon_size));
|
entry.lead(icon.size(metrics.icon_size))
|
||||||
}
|
})
|
||||||
entry.closeable(true).wrap({
|
.closeable(true)
|
||||||
let panel = tab.panel.clone();
|
.wrap({
|
||||||
let title = tab.title.clone();
|
let panel = tab.panel.clone();
|
||||||
move |tab| {
|
let title = tab.title.clone();
|
||||||
tab.on_drag(
|
move |tab| {
|
||||||
PanelDrag {
|
tab.on_drag(
|
||||||
panel: panel.clone(),
|
PanelDrag {
|
||||||
},
|
panel: panel.clone(),
|
||||||
{
|
},
|
||||||
let title = title.clone();
|
{
|
||||||
move |_, _, _, cx| {
|
let title = title.clone();
|
||||||
cx.new(|_| DragPreview {
|
move |_, _, _, cx| {
|
||||||
title: title.clone(),
|
cx.new(|_| DragPreview {
|
||||||
})
|
title: title.clone(),
|
||||||
}
|
})
|
||||||
},
|
}
|
||||||
)
|
},
|
||||||
.into_any_element()
|
)
|
||||||
}
|
.into_any_element()
|
||||||
})
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,39 +220,36 @@ impl RenderOnce for Resizable {
|
|||||||
.map(|ix| state.read(cx).pane_size(ix, pane_count))
|
.map(|ix| state.read(cx).pane_size(ix, pane_count))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut group = div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.when(axis == Axis::Vertical, |group| group.flex_col())
|
.when(axis == Axis::Vertical, |group| group.flex_col())
|
||||||
.when(axis == Axis::Horizontal, |group| group.flex_row())
|
.when(axis == Axis::Horizontal, |group| group.flex_row())
|
||||||
.refine_style(&self.style);
|
.refine_style(&self.style)
|
||||||
|
.children(self.panes.into_iter().enumerate().flat_map(|(ix, pane)| {
|
||||||
for (ix, pane) in self.panes.into_iter().enumerate() {
|
let remainder = ix + 1 == pane_count;
|
||||||
let remainder = ix + 1 == pane_count;
|
let pane_element = div()
|
||||||
let content = pane.content;
|
|
||||||
let mut pane_element = if remainder {
|
|
||||||
div()
|
|
||||||
.flex_1()
|
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.when(axis == Axis::Vertical, |p| p.min_h(pane.min))
|
.when(remainder, |pane_element| {
|
||||||
.when(axis == Axis::Horizontal, |p| p.min_w(pane.min))
|
pane_element
|
||||||
} else {
|
.flex_1()
|
||||||
div()
|
.when(axis == Axis::Vertical, |p| p.min_h(pane.min))
|
||||||
.flex_none()
|
.when(axis == Axis::Horizontal, |p| p.min_w(pane.min))
|
||||||
.overflow_hidden()
|
})
|
||||||
.when(axis == Axis::Vertical, |p| p.h(sizes[ix]).w_full())
|
.when(!remainder, |pane_element| {
|
||||||
.when(axis == Axis::Horizontal, |p| p.w(sizes[ix]).h_full())
|
pane_element
|
||||||
};
|
.flex_none()
|
||||||
|
.when(axis == Axis::Vertical, |p| p.h(sizes[ix]).w_full())
|
||||||
|
.when(axis == Axis::Horizontal, |p| p.w(sizes[ix]).h_full())
|
||||||
|
})
|
||||||
|
.when_some(debug_selector.clone(), |pane_element, selector| {
|
||||||
|
let prefix = selector();
|
||||||
|
pane_element.debug_selector(move || format!("{prefix}-{ix}"))
|
||||||
|
})
|
||||||
|
.child(pane.content)
|
||||||
|
.into_any_element();
|
||||||
|
|
||||||
if let Some(selector) = &debug_selector {
|
let divider = (!remainder).then(|| {
|
||||||
let prefix = selector();
|
|
||||||
pane_element = pane_element.debug_selector(move || format!("{prefix}-{ix}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
group = group.child(pane_element.child(content));
|
|
||||||
|
|
||||||
if !remainder {
|
|
||||||
let divider =
|
|
||||||
ResizeHandle::new(SharedString::from(format!("{}-divider-{ix}", self.id)))
|
ResizeHandle::new(SharedString::from(format!("{}-divider-{ix}", self.id)))
|
||||||
.axis(axis)
|
.axis(axis)
|
||||||
.on_resize({
|
.on_resize({
|
||||||
@@ -268,32 +265,30 @@ impl RenderOnce for Resizable {
|
|||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
state.update(cx, |state, cx| state.reset_pane(ix, mins.len(), cx));
|
state.update(cx, |state, cx| state.reset_pane(ix, mins.len(), cx));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.into_any_element()
|
||||||
|
});
|
||||||
|
|
||||||
group = group.child(divider);
|
std::iter::once(pane_element).chain(divider)
|
||||||
}
|
}))
|
||||||
}
|
.when(pane_count > 0, |group| {
|
||||||
|
group.child(
|
||||||
if pane_count > 0 {
|
canvas(
|
||||||
group = group.child(
|
{
|
||||||
canvas(
|
let state = state.clone();
|
||||||
{
|
move |bounds: Bounds<Pixels>, _, cx: &mut App| {
|
||||||
let state = state.clone();
|
let extent = match axis {
|
||||||
move |bounds: Bounds<Pixels>, _, cx: &mut App| {
|
Axis::Vertical => bounds.size.height,
|
||||||
let extent = match axis {
|
Axis::Horizontal => bounds.size.width,
|
||||||
Axis::Vertical => bounds.size.height,
|
};
|
||||||
Axis::Horizontal => bounds.size.width,
|
state.update(cx, |state, cx| state.set_measured(extent, cx));
|
||||||
};
|
}
|
||||||
state.update(cx, |state, cx| state.set_measured(extent, cx));
|
},
|
||||||
}
|
|_, _, _, _| {},
|
||||||
},
|
)
|
||||||
|_, _, _, _| {},
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
)
|
)
|
||||||
.absolute()
|
})
|
||||||
.inset_0(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
group
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -9,12 +9,16 @@ is the first downstream consumer; its screens drive the order.
|
|||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
- [ ] **Declarative render audit** — run the AGENTS.md render rule over the
|
- [x] **Declarative render audit** — the AGENTS.md render rule swept the
|
||||||
other landed components: replace `let mut` element variables reassigned
|
landed components: `let mut` element variables reassigned under `if` /
|
||||||
under `if` / `if let` with one chained expression using `.when(...)` /
|
`if let` now build one chained expression with `.when(...)` /
|
||||||
`.when_some(...)` / `.map(...)`. The sidebar row is the model; sweep
|
`.when_some(...)` / `.map(...)`. Converted: Tabs strip root and tab,
|
||||||
`crates/core`, `crates/data`, `crates/editor`, `crates/layout`, and
|
Menu / DropdownMenu / Popover / Combobox wrappers, ScrollArea pane and
|
||||||
re-run tests plus a gallery pass after each crate.
|
scrollbars, Select rows, Dock tab entries, Resizable panes and dividers,
|
||||||
|
the Markdown text / code / table rows, and the Diff gutter cell.
|
||||||
|
`MenuItem` and `Tab` adopted `FluentBuilder` so their builders chain
|
||||||
|
too; data accumulators (`Vec`/`String` builders, style clones) stay
|
||||||
|
imperative — they are state, not element refinement.
|
||||||
|
|
||||||
## Queue — to follow
|
## Queue — to follow
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user