first pass of merging in warp (doesn't build)
This commit is contained in:
+274
-37
@@ -2,35 +2,37 @@ use std::cell::OnceCell;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, vec};
|
||||
|
||||
use crate::safe_triangle::SafeTriangle;
|
||||
use crate::themes::theme::Fill;
|
||||
use crate::util::time_format::format_approx_duration_from_now_sentence_case;
|
||||
use crate::{appearance::Appearance, ui_components::icons};
|
||||
use chrono::{DateTime, Local};
|
||||
use galaxy_core::ui::color::blend::Blend;
|
||||
use galaxyui::elements::{
|
||||
ChildAnchor, ClippedScrollStateHandle, ClippedScrollable, DropShadow, OffsetPositioning,
|
||||
ParentAnchor, ParentOffsetBounds, PositionedElementAnchor, PositionedElementOffsetBounds,
|
||||
ScrollTarget, ScrollToPositionMode, ScrollbarWidth, Stack,
|
||||
};
|
||||
use galaxyui::WindowId;
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
elements::{
|
||||
Align, Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Dismiss,
|
||||
DispatchEventResult, Element, EventHandler, Flex, Hoverable, Icon, MainAxisAlignment,
|
||||
MainAxisSize, MouseInBehavior, MouseStateHandle, ParentElement, Radius, Rect, SavePosition,
|
||||
Shrinkable, Text,
|
||||
},
|
||||
fonts::{FamilyId, Properties},
|
||||
keymap::FixedBinding,
|
||||
platform::Cursor,
|
||||
ui_components::components::UiComponent,
|
||||
Action, AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
};
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use galaxy_core::ui::color::blend::Blend;
|
||||
use galaxyui::accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole};
|
||||
use galaxyui::assets::asset_cache::AssetSource;
|
||||
use galaxyui::elements::{
|
||||
Align, Border, CacheOption, ChildAnchor, ClippedScrollStateHandle, ClippedScrollable,
|
||||
ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Dismiss, DispatchEventResult,
|
||||
DropShadow, Element, EventHandler, Flex, Hoverable, Icon, Image, MainAxisAlignment,
|
||||
MainAxisSize, MouseInBehavior, MouseStateHandle, OffsetPositioning, ParentAnchor,
|
||||
ParentElement, ParentOffsetBounds, PositionedElementAnchor, PositionedElementOffsetBounds,
|
||||
Radius, Rect, SavePosition, ScrollTarget, ScrollToPositionMode, ScrollbarWidth, Shrinkable,
|
||||
Stack, Text,
|
||||
};
|
||||
use galaxyui::fonts::{FamilyId, Properties};
|
||||
use galaxyui::keymap::FixedBinding;
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::text_layout::ClipConfig;
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
use galaxyui::{
|
||||
Action, AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, WindowId,
|
||||
};
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::safe_triangle::SafeTriangle;
|
||||
use crate::themes::theme::Fill;
|
||||
use crate::ui_components::buttons::icon_button_with_color;
|
||||
use crate::ui_components::icons;
|
||||
use crate::util::time_format::format_approx_duration_from_now_sentence_case;
|
||||
|
||||
pub const CHEVRON_RIGHT_ALIGN_SVG_PATH: &str = "bundled/svg/chevron-right-align.svg";
|
||||
|
||||
@@ -123,6 +125,9 @@ pub struct Menu<A: Action + Clone = ()> {
|
||||
/// Optional overrides for the depth-0 menu content padding.
|
||||
content_top_padding_override: Option<f32>,
|
||||
content_bottom_padding_override: Option<f32>,
|
||||
/// Optional saved-position id whose last rendered width should override
|
||||
/// the configured submenu width while this menu is rendered.
|
||||
width_match_position_id: Option<String>,
|
||||
/// If false, selecting a menu item updates selection and emits menu events
|
||||
/// without dispatching the item's typed action directly from the menu.
|
||||
dispatch_item_actions: bool,
|
||||
@@ -261,18 +266,20 @@ impl MenuItemLabel {
|
||||
menu_width: f32,
|
||||
is_selected: bool,
|
||||
is_hovered: bool,
|
||||
clip_config: Option<ClipConfig>,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
match self {
|
||||
Self::Text(label) => Shrinkable::new(
|
||||
4.,
|
||||
Text::new_inline(label.clone(), font_family, font_size)
|
||||
Self::Text(label) => {
|
||||
let mut text = Text::new_inline(label.clone(), font_family, font_size)
|
||||
.with_color(primary_color.into())
|
||||
.autosize_text(MINIMUM_MENU_ITEM_FONT_SIZE)
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
.autosize_text(MINIMUM_MENU_ITEM_FONT_SIZE);
|
||||
if let Some(config) = clip_config {
|
||||
text = text.with_clip(config).soft_wrap(false);
|
||||
}
|
||||
Shrinkable::new(4., text.finish()).finish()
|
||||
}
|
||||
Self::MultilineText { label, max_lines } => {
|
||||
let max_height_for_n_lines =
|
||||
*max_lines as f32 * font_size * appearance.line_height_ratio();
|
||||
@@ -385,6 +392,47 @@ struct RightSideLabel {
|
||||
text: String,
|
||||
font_properties: Properties,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct RightSideIconConfig<A> {
|
||||
icon: icons::Icon,
|
||||
override_color: Option<Fill>,
|
||||
/// Optional action dispatched when the right-side icon is clicked. When
|
||||
/// set, the right-side icon becomes its own hit target: clicking it
|
||||
/// dispatches this action without firing the row's own `on_select_action`,
|
||||
/// and prevents the row click from propagating.
|
||||
action: Option<A>,
|
||||
/// Optional accessibility label for the right-side icon hit target.
|
||||
a11y_label: Option<String>,
|
||||
/// When true, the right-side icon is rendered as disabled with no hover or
|
||||
/// click action.
|
||||
disabled: bool,
|
||||
/// Tracks hover state independently from the row.
|
||||
mouse_state: MouseStateHandle,
|
||||
}
|
||||
|
||||
impl<A> RightSideIconConfig<A> {
|
||||
fn new(icon: icons::Icon) -> Self {
|
||||
Self {
|
||||
icon,
|
||||
override_color: None,
|
||||
action: None,
|
||||
a11y_label: None,
|
||||
disabled: false,
|
||||
mouse_state: MouseStateHandle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn without_action<B>(self) -> RightSideIconConfig<B> {
|
||||
RightSideIconConfig {
|
||||
icon: self.icon,
|
||||
override_color: self.override_color,
|
||||
action: None,
|
||||
a11y_label: self.a11y_label,
|
||||
disabled: self.disabled,
|
||||
mouse_state: self.mouse_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MenuItemFields<A: Action + Clone> {
|
||||
@@ -396,6 +444,9 @@ pub struct MenuItemFields<A: Action + Clone> {
|
||||
disabled: bool,
|
||||
mouse_state: MouseStateHandle,
|
||||
icon: Option<icons::Icon>,
|
||||
/// Path to a full-color image asset (e.g. `bundled/svg/file_type/rust.svg`).
|
||||
/// When set, the icon is rendered via [`Image::new`] preserving original colors.
|
||||
image_icon: Option<&'static str>,
|
||||
override_icon_color: Option<Fill>,
|
||||
override_text_color: Option<ColorU>,
|
||||
override_font_family: Option<FamilyId>,
|
||||
@@ -408,7 +459,8 @@ pub struct MenuItemFields<A: Action + Clone> {
|
||||
tooltip: Option<String>,
|
||||
tooltip_position: MenuTooltipPosition,
|
||||
right_side_label: Option<RightSideLabel>,
|
||||
/// Optional override for the background color rendered when this item is
|
||||
right_side_icon: Option<RightSideIconConfig<A>>,
|
||||
/// Optional override for the background color
|
||||
/// hovered or selected. When `None`, the default hover/selected background
|
||||
/// from the theme is used (accent or dark overlay, depending on
|
||||
/// `highlight_on_hover`).
|
||||
@@ -416,6 +468,11 @@ pub struct MenuItemFields<A: Action + Clone> {
|
||||
/// Optional override for the leading icon size in logical pixels. When
|
||||
/// `None`, the icon is sized to `appearance.ui_font_size()`.
|
||||
icon_size_override: Option<f32>,
|
||||
/// Optional clip config controlling how the label text is clipped when it
|
||||
/// would overflow the available width. Only applied to plain
|
||||
/// [`MenuItemLabel::Text`] labels — multiline/stacked/labeled/icon/custom
|
||||
/// variants ignore this field.
|
||||
clip_config: Option<ClipConfig>,
|
||||
}
|
||||
|
||||
impl<A: Action + Clone> std::fmt::Debug for MenuItemFields<A> {
|
||||
@@ -439,6 +496,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -452,8 +510,10 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,6 +526,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -479,8 +540,10 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,6 +559,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -509,12 +573,14 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new menu item with vertically stacked primary and secondary text.
|
||||
/// Creates a new menu item with vertically stacked
|
||||
/// This is useful for items that need both a title and description/subtitle,
|
||||
/// such as slash commands with their descriptions.
|
||||
pub fn new_with_stacked_label<T: Into<String>>(title: T, subtitle: T) -> Self {
|
||||
@@ -529,6 +595,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -542,8 +609,10 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,6 +629,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -573,8 +643,10 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +662,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -603,8 +676,10 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,6 +692,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: false,
|
||||
mouse_state: Default::default(),
|
||||
icon: None,
|
||||
image_icon: None,
|
||||
override_icon_color: None,
|
||||
override_font_family: None,
|
||||
override_font_size: None,
|
||||
@@ -630,8 +706,10 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: None,
|
||||
tooltip_position: MenuTooltipPosition::default(),
|
||||
right_side_label: None,
|
||||
right_side_icon: None,
|
||||
override_hover_background_color: None,
|
||||
icon_size_override: None,
|
||||
clip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +738,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
disabled: self.disabled,
|
||||
mouse_state: self.mouse_state,
|
||||
icon: self.icon,
|
||||
image_icon: self.image_icon,
|
||||
override_icon_color: self.override_icon_color,
|
||||
override_font_family: self.override_font_family,
|
||||
override_font_size: self.override_font_size,
|
||||
@@ -673,8 +752,16 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
tooltip: self.tooltip,
|
||||
tooltip_position: self.tooltip_position,
|
||||
right_side_label: self.right_side_label,
|
||||
// The right-side icon action is `Option<A>`; we can't safely map
|
||||
// it to `Option<B>` here, so drop it. Callers that need the
|
||||
// right-side action must set it via `with_right_side_icon_action`
|
||||
// after conversion.
|
||||
right_side_icon: self
|
||||
.right_side_icon
|
||||
.map(RightSideIconConfig::without_action),
|
||||
override_hover_background_color: self.override_hover_background_color,
|
||||
icon_size_override: self.icon_size_override,
|
||||
clip_config: self.clip_config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,6 +841,14 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a full-color image asset as the icon for this menu item.
|
||||
/// The image is rendered via [`Image::new`], preserving its original colors
|
||||
/// (e.g. for language logos from `bundled/svg/file_type/`).
|
||||
pub fn with_image_icon(mut self, path: &'static str) -> Self {
|
||||
self.image_icon = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_indent(mut self) -> Self {
|
||||
self.indent = true;
|
||||
self
|
||||
@@ -772,6 +867,14 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a [`ClipConfig`] that controls how the label text is clipped when
|
||||
/// it would overflow the available width. Only applied to plain
|
||||
/// [`MenuItemLabel::Text`] labels.
|
||||
pub fn with_clip_config(mut self, config: ClipConfig) -> Self {
|
||||
self.clip_config = Some(config);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_tooltip_position(mut self, position: MenuTooltipPosition) -> Self {
|
||||
self.tooltip_position = position;
|
||||
self
|
||||
@@ -790,6 +893,36 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_right_side_icon(mut self, icon: icons::Icon) -> Self {
|
||||
self.right_side_icon = Some(RightSideIconConfig::new(icon));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a separate action that fires when the right-side icon is
|
||||
/// clicked. The action is independent from the row's
|
||||
/// `on_select_action`: clicking the icon dispatches this action and
|
||||
/// the row click is suppressed.
|
||||
pub fn with_right_side_icon_action(mut self, action: A) -> Self {
|
||||
if let Some(config) = &mut self.right_side_icon {
|
||||
config.action = Some(action);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_right_side_icon_a11y_label(mut self, label: impl Into<String>) -> Self {
|
||||
if let Some(config) = &mut self.right_side_icon {
|
||||
config.a11y_label = Some(label.into());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_right_side_icon_disabled(mut self, disabled: bool) -> Self {
|
||||
if let Some(config) = &mut self.right_side_icon {
|
||||
config.disabled = disabled;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_item(self) -> MenuItem<A> {
|
||||
MenuItem::Item(self)
|
||||
}
|
||||
@@ -825,6 +958,25 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
}
|
||||
|
||||
fn render_icon(&self, appearance: &Appearance, color: Fill) -> Option<Box<dyn Element>> {
|
||||
let icon_size = appearance.ui_font_size();
|
||||
if let Some(path) = self.image_icon {
|
||||
return Some(
|
||||
Shrinkable::new(
|
||||
1.,
|
||||
Container::new(
|
||||
ConstrainedBox::new(
|
||||
Image::new(AssetSource::Bundled { path }, CacheOption::BySize).finish(),
|
||||
)
|
||||
.with_width(icon_size)
|
||||
.with_height(icon_size)
|
||||
.finish(),
|
||||
)
|
||||
.with_margin_right(icon_size / 2.)
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
if let Some(icon) = self.icon {
|
||||
let icon_size = self
|
||||
.icon_size_override
|
||||
@@ -903,6 +1055,63 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_right_side_icon(
|
||||
&self,
|
||||
appearance: &Appearance,
|
||||
color: Fill,
|
||||
dispatch_item_actions: bool,
|
||||
) -> Option<Box<dyn Element>> {
|
||||
let config = self.right_side_icon.as_ref()?;
|
||||
let icon_size = self
|
||||
.icon_size_override
|
||||
.unwrap_or_else(|| appearance.ui_font_size());
|
||||
let icon_color = config.override_color.unwrap_or(color);
|
||||
if let Some(action) = &config.action {
|
||||
let mut button = icon_button_with_color(
|
||||
appearance,
|
||||
config.icon,
|
||||
false,
|
||||
config.mouse_state.clone(),
|
||||
icon_color,
|
||||
);
|
||||
if config.disabled {
|
||||
button = button.disabled();
|
||||
}
|
||||
let mut hoverable = button.build();
|
||||
if !config.disabled {
|
||||
let action = action.clone();
|
||||
hoverable = hoverable.on_click(move |ctx, _, _| {
|
||||
if dispatch_item_actions {
|
||||
ctx.dispatch_typed_action(action.clone());
|
||||
}
|
||||
});
|
||||
// Swallow mouse-down too so the row's click handler
|
||||
// doesn't latch onto the press that targets the icon.
|
||||
hoverable = hoverable.on_mouse_down(|_, _, _| {});
|
||||
}
|
||||
let button_element = if config.disabled {
|
||||
EventHandler::new(hoverable.finish())
|
||||
.on_left_mouse_down(|_, _, _| DispatchEventResult::StopPropagation)
|
||||
.on_left_mouse_up(|_, _, _| DispatchEventResult::StopPropagation)
|
||||
.finish()
|
||||
} else {
|
||||
hoverable.finish()
|
||||
};
|
||||
let element = Container::new(button_element)
|
||||
.with_margin_left(icon_size / 2.)
|
||||
.finish();
|
||||
return Some(Shrinkable::new(1., Align::new(element).right().finish()).finish());
|
||||
}
|
||||
let icon_element = ConstrainedBox::new(config.icon.to_warpui_icon(icon_color).finish())
|
||||
.with_width(icon_size)
|
||||
.with_height(icon_size)
|
||||
.finish();
|
||||
let container = Container::new(icon_element)
|
||||
.with_margin_left(icon_size / 2.)
|
||||
.finish();
|
||||
Some(Shrinkable::new(1., Align::new(container).right().finish()).finish())
|
||||
}
|
||||
|
||||
fn render_right_aligned_chevron(
|
||||
&self,
|
||||
appearance: &Appearance,
|
||||
@@ -1046,6 +1255,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
menu_width,
|
||||
is_selected,
|
||||
state.is_hovered(),
|
||||
self.clip_config,
|
||||
appearance,
|
||||
app,
|
||||
);
|
||||
@@ -1092,6 +1302,12 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
appearance,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(right_icon) =
|
||||
self.render_right_side_icon(appearance, primary_color, dispatch_item_actions)
|
||||
{
|
||||
label_row.add_child(right_icon);
|
||||
}
|
||||
}
|
||||
|
||||
// If menu item doesn't have an icon but we want to indent it, add left padding so it aligns with menu items that do have icons
|
||||
@@ -1188,6 +1404,7 @@ impl<A: Action + Clone> MenuItemFields<A> {
|
||||
});
|
||||
}
|
||||
});
|
||||
ret = ret.with_defer_events_to_children();
|
||||
|
||||
let on_select_action = self.on_select_action.clone();
|
||||
|
||||
@@ -2036,6 +2253,7 @@ impl<A: Action + Clone> Menu<A> {
|
||||
pinned_header_builder: None,
|
||||
content_top_padding_override: None,
|
||||
content_bottom_padding_override: None,
|
||||
width_match_position_id: None,
|
||||
dispatch_item_actions: true,
|
||||
}
|
||||
}
|
||||
@@ -2118,6 +2336,9 @@ impl<A: Action + Clone> Menu<A> {
|
||||
pub fn set_width(&mut self, width: f32) {
|
||||
self.submenu_width = width;
|
||||
}
|
||||
pub fn set_width_match_position_id(&mut self, position_id: Option<String>) {
|
||||
self.width_match_position_id = position_id;
|
||||
}
|
||||
|
||||
pub fn set_border(&mut self, border: Option<Border>) {
|
||||
self.border = border;
|
||||
@@ -2255,6 +2476,11 @@ impl<A: Action + Clone> Menu<A> {
|
||||
self.select(SelectAction::Next, ctx);
|
||||
}
|
||||
|
||||
/// Select the first selectable item in the menu. No-op if no item is selectable.
|
||||
pub fn select_first(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.menu.select_first_selectable(ctx);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mimic_confirm(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
if let Some(item) = self.selected_item() {
|
||||
@@ -2664,13 +2890,24 @@ impl<A: Action + Clone> View for Menu<A> {
|
||||
.as_ref()
|
||||
.filter(|st| st.is_suppressing())
|
||||
.and(self.menu.hovered_row_index);
|
||||
let window_id = self.window_id(app);
|
||||
let submenu_width = self
|
||||
.width_match_position_id
|
||||
.as_deref()
|
||||
.and_then(|position_id| {
|
||||
window_id.and_then(|window_id| {
|
||||
app.element_position_by_id_at_last_frame(window_id, position_id)
|
||||
})
|
||||
})
|
||||
.map(|bounds| bounds.width())
|
||||
.unwrap_or(self.submenu_width);
|
||||
|
||||
self.menu.render(
|
||||
self.border,
|
||||
self.submenu_width,
|
||||
submenu_width,
|
||||
self.with_drop_shadow,
|
||||
self.origin,
|
||||
self.window_id(app),
|
||||
window_id,
|
||||
self.prevent_interaction_with_other_elements,
|
||||
self.dispatch_item_actions,
|
||||
self.ignore_hover_when_covered,
|
||||
@@ -2730,5 +2967,5 @@ impl<A: Action + Clone> MenuItem<A> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "menu_test.rs"]
|
||||
#[path = "menu_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user