From 151055269f391431652aa8227a8cf328a6bcab8d Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Fri, 4 Sep 2026 20:02:35 +0300 Subject: [PATCH] context menu support for tab control headers --- editor/src/plugins/tilemap/tileset.rs | 1 + editor/src/scene_viewer/mod.rs | 1 + fyrox-ui/src/dock/tile.rs | 2 + fyrox-ui/src/tab_control.rs | 153 ++++++++++++++------------ fyrox-ui/src/widget.rs | 6 + 5 files changed, 90 insertions(+), 73 deletions(-) diff --git a/editor/src/plugins/tilemap/tileset.rs b/editor/src/plugins/tilemap/tileset.rs index ae9998982..4a1cb51a7 100644 --- a/editor/src/plugins/tilemap/tileset.rs +++ b/editor/src/plugins/tilemap/tileset.rs @@ -140,6 +140,7 @@ fn make_tab( content, can_be_closed: false, user_data: None, + context_menu: None, } } diff --git a/editor/src/scene_viewer/mod.rs b/editor/src/scene_viewer/mod.rs index b02983880..35df2e081 100644 --- a/editor/src/scene_viewer/mod.rs +++ b/editor/src/scene_viewer/mod.rs @@ -920,6 +920,7 @@ impl SceneViewer { content: Default::default(), can_be_closed: true, user_data: None, + context_menu: None, }), ); } diff --git a/fyrox-ui/src/dock/tile.rs b/fyrox-ui/src/dock/tile.rs index 1c67c9917..4db88cf96 100644 --- a/fyrox-ui/src/dock/tile.rs +++ b/fyrox-ui/src/dock/tile.rs @@ -1040,6 +1040,7 @@ impl Tile { header, content: Handle::NONE, user_data: None, + context_menu: None, }; ui.send(self.tabs, TabControlMessage::AddTab(definition)); } @@ -1327,6 +1328,7 @@ impl TileBuilder { content: Handle::NONE, user_data: None, header, + context_menu: None, }; tabs = tabs.with_tab(definition); } diff --git a/fyrox-ui/src/tab_control.rs b/fyrox-ui/src/tab_control.rs index 919d22208..a9fba8b64 100644 --- a/fyrox-ui/src/tab_control.rs +++ b/fyrox-ui/src/tab_control.rs @@ -39,8 +39,8 @@ use crate::{ vector_image::VectorImageBuilder, widget::{Widget, WidgetBuilder, WidgetMessage}, wrap_panel::{WrapPanel, WrapPanelBuilder}, - BuildContext, Control, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface, - VerticalAlignment, + BuildContext, Control, HorizontalAlignment, Orientation, RcUiNodeHandle, Thickness, UiNode, + UserInterface, VerticalAlignment, }; use fyrox_core::variable::InheritableVariable; use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor}; @@ -495,6 +495,8 @@ pub struct TabDefinition { pub can_be_closed: bool, /// User-defined data. pub user_data: Option, + /// An option context menu that will be used on the tab header. + pub context_menu: Option, } struct Header { @@ -514,79 +516,84 @@ impl Header { let close_button; let decorator; - let button = ButtonBuilder::new(WidgetBuilder::new().on_row(0).on_column(0)) - .with_back({ - decorator = DecoratorBuilder::new( - BorderBuilder::new(WidgetBuilder::new().with_margin(Thickness { - left: 0.0, - top: 2.0, - right: 2.0, - bottom: 2.0, - })) - .with_stroke_thickness(Thickness::uniform(0.0).into()) - .with_pad_by_corner_radius(false) - .with_corner_radius(4.0.into()), - ) - .with_normal_brush(ctx.style.property(Style::BRUSH_DARK)) - .with_selected_brush(active_tab_brush) - .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST)) - .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHT)) - .with_selected(selected) - .build(ctx); - decorator - }) - .with_content( - GridBuilder::new( - WidgetBuilder::new() - .with_child(tab_definition.header) - .with_child({ - close_button = if tab_definition.can_be_closed { - ButtonBuilder::new( - WidgetBuilder::new() - .with_margin(Thickness::right(1.0)) - .on_row(0) - .on_column(1) - .with_width(16.0) - .with_height(16.0), - ) - .with_back( - DecoratorBuilder::new( - BorderBuilder::new(WidgetBuilder::new()) - .with_corner_radius(5.0f32.into()) - .with_pad_by_corner_radius(false) - .with_stroke_thickness(Thickness::uniform(0.0).into()), - ) - .with_normal_brush(Brush::Solid(Color::TRANSPARENT).into()) - .with_hover_brush(ctx.style.property(Style::BRUSH_DARK)) - .build(ctx), - ) - .with_content( - VectorImageBuilder::new( - WidgetBuilder::new() - .with_horizontal_alignment(HorizontalAlignment::Center) - .with_vertical_alignment(VerticalAlignment::Center) - .with_width(8.0) - .with_height(8.0) - .with_foreground( - ctx.style.property(Style::BRUSH_BRIGHTEST), - ), - ) - .with_primitives(make_cross_primitive(8.0, 2.0)) - .build(ctx), - ) - .build(ctx) - } else { - Handle::NONE - }; - close_button - }), - ) - .add_row(Row::auto()) - .add_column(Column::stretch()) - .add_column(Column::auto()) - .build(ctx), + let button = ButtonBuilder::new( + WidgetBuilder::new() + .on_row(0) + .on_column(0) + .with_opt_context_menu(tab_definition.context_menu.clone()), + ) + .with_back({ + decorator = DecoratorBuilder::new( + BorderBuilder::new(WidgetBuilder::new().with_margin(Thickness { + left: 0.0, + top: 2.0, + right: 2.0, + bottom: 2.0, + })) + .with_stroke_thickness(Thickness::uniform(0.0).into()) + .with_pad_by_corner_radius(false) + .with_corner_radius(4.0.into()), ) + .with_normal_brush(ctx.style.property(Style::BRUSH_DARK)) + .with_selected_brush(active_tab_brush) + .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST)) + .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHT)) + .with_selected(selected) .build(ctx); + decorator + }) + .with_content( + GridBuilder::new( + WidgetBuilder::new() + .with_child(tab_definition.header) + .with_child({ + close_button = if tab_definition.can_be_closed { + ButtonBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::right(1.0)) + .on_row(0) + .on_column(1) + .with_width(16.0) + .with_height(16.0), + ) + .with_back( + DecoratorBuilder::new( + BorderBuilder::new(WidgetBuilder::new()) + .with_corner_radius(5.0f32.into()) + .with_pad_by_corner_radius(false) + .with_stroke_thickness(Thickness::uniform(0.0).into()), + ) + .with_normal_brush(Brush::Solid(Color::TRANSPARENT).into()) + .with_hover_brush(ctx.style.property(Style::BRUSH_DARK)) + .build(ctx), + ) + .with_content( + VectorImageBuilder::new( + WidgetBuilder::new() + .with_horizontal_alignment(HorizontalAlignment::Center) + .with_vertical_alignment(VerticalAlignment::Center) + .with_width(8.0) + .with_height(8.0) + .with_foreground( + ctx.style.property(Style::BRUSH_BRIGHTEST), + ), + ) + .with_primitives(make_cross_primitive(8.0, 2.0)) + .build(ctx), + ) + .build(ctx) + } else { + Handle::NONE + }; + close_button + }), + ) + .add_row(Row::auto()) + .add_column(Column::stretch()) + .add_column(Column::auto()) + .build(ctx), + ) + .build(ctx); Header { button, diff --git a/fyrox-ui/src/widget.rs b/fyrox-ui/src/widget.rs index 17d9b0d55..6902dfeae 100644 --- a/fyrox-ui/src/widget.rs +++ b/fyrox-ui/src/widget.rs @@ -2071,6 +2071,12 @@ impl WidgetBuilder { self } + /// The context menu receives `PopupMessage`s for being displayed, and so should support those. + pub fn with_opt_context_menu(mut self, context_menu: Option) -> Self { + self.context_menu = context_menu; + self + } + /// Sets the desired tab index. pub fn with_tab_index(mut self, tab_index: Option) -> Self { self.tab_index = tab_index;