From e4e5e193e73c2ffc8ce0d522216c8147920e4721 Mon Sep 17 00:00:00 2001 From: Adanos020 Date: Sat, 25 Nov 2023 20:51:59 +0000 Subject: [PATCH 1/5] Bump crate version --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a46b88..725ff73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "egui_dock" description = "Docking system for egui - an immediate-mode GUI library for Rust" authors = ["lain-dono", "Adam Gąsior (Adanos020)"] -version = "0.9.1" +version = "0.10.0" edition = "2021" rust-version = "1.72" license = "MIT" diff --git a/README.md b/README.md index 00dbe54..d5a605b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add `egui` and `egui_dock` to your project's dependencies. ```toml [dependencies] egui = "0.24" -egui_dock = "0.9" +egui_dock = "0.10" ``` Then proceed by setting up `egui`, following its [quick start guide](https://github.com/emilk/egui#quick-start). From c2066c5ddb367fef1b806ad86d9fb4e4de71b585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 31 Dec 2023 14:22:22 -0500 Subject: [PATCH 2/5] Keyboard focus fixes (#211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix crash after calling `DockState::remove_tab`. (#208) * Remove surface after removing the last remaining tab, if the surface isn't `Main`. * Add explicit panic in `Tree::remove_leaf` if the `Tree` is empty. * Bump patch version, update changelog. * Include instructions on how to run examples in Readme. (#209) * Fix keyboard focus not working inside `DockArea`s * Add visual indicators when tab buttons are focused * Allow current tab to be switched using the keyboard * Remove extra focusable areas I removed focus from two widgets that are not supposed to be focusable. Also, I added highlighting to the separators when they are focused because apparently I can't remove the ability to focus them without breaking the ability to drag the separators. * Separators can now be moved with the arrow keys * Add highlighting to active tabs that are keyboard focused * Update changelog * Add tab styles for keyboard-focused tabs * Don't move separators unless Ctrl or Shift is down This is so that using the arrow keys to change the keyboard focus will still work normally. * Clippy --------- Co-authored-by: Adam Gąsior --- CHANGELOG.md | 11 +++++ src/style.rs | 66 ++++++++++++++++++++++++++++++ src/widgets/dock_area/show/leaf.rs | 50 ++++++++++++++-------- src/widgets/dock_area/show/mod.rs | 36 +++++++++++++--- 4 files changed, 141 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfeaa81..0b312d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # egui_dock changelog +## 0.10.0 - TBD + +### Added +From ([#211](https://github.com/Adanos020/egui_dock/pull/211)): + - Tabs, the close tab buttons and the add tab buttons are now focusable with the keyboard and interactable with the enter key and space bar. + - Separators are now focusable with the keyboard and movable using the arrow keys while control or shift is held. + - `TabStyle::active_with_kb_focus`, `TabStyle::inactive_with_kb_focus` and `TabStyle::focused_with_kb_focus` for style of tabs that are focused with the keyboard. + +### Fixed +- Widgets inside tabs are now focusable with the tab key on the keyboard. ([#211](https://github.com/Adanos020/egui_dock/pull/211)) + ## 0.9.1 - 2023-12-10 ### Fixed diff --git a/src/style.rs b/src/style.rs index 2fe7c68..93042ad 100644 --- a/src/style.rs +++ b/src/style.rs @@ -151,6 +151,15 @@ pub struct TabStyle { /// Style of the tab when it is hovered. pub hovered: TabInteractionStyle, + /// Style of the tab when it is inactive and has keyboard focus. + pub inactive_with_kb_focus: TabInteractionStyle, + + /// Style of the tab when it is active and has keyboard focus. + pub active_with_kb_focus: TabInteractionStyle, + + /// Style of the tab when it is focused and has keyboard focus. + pub focused_with_kb_focus: TabInteractionStyle, + /// Style for the tab body. pub tab_body: TabBodyStyle, @@ -357,6 +366,15 @@ impl Default for TabStyle { text_color: Color32::BLACK, ..Default::default() }, + active_with_kb_focus: TabInteractionStyle::default(), + inactive_with_kb_focus: TabInteractionStyle { + text_color: Color32::DARK_GRAY, + ..Default::default() + }, + focused_with_kb_focus: TabInteractionStyle { + text_color: Color32::BLACK, + ..Default::default() + }, tab_body: TabBodyStyle::default(), hline_below_active_tab_name: false, minimum_width: None, @@ -532,6 +550,9 @@ impl TabStyle { inactive: TabInteractionStyle::from_egui_inactive(style), focused: TabInteractionStyle::from_egui_focused(style), hovered: TabInteractionStyle::from_egui_hovered(style), + active_with_kb_focus: TabInteractionStyle::from_egui_active_with_kb_focus(style), + inactive_with_kb_focus: TabInteractionStyle::from_egui_inactive_with_kb_focus(style), + focused_with_kb_focus: TabInteractionStyle::from_egui_focused_with_kb_focus(style), tab_body: TabBodyStyle::from_egui(style), ..Default::default() } @@ -609,6 +630,51 @@ impl TabInteractionStyle { ..TabInteractionStyle::from_egui_inactive(style) } } + + /// Derives relevant fields from `egui::Style` for an active tab with keyboard focus and sets the remaining fields to their default values. + /// + /// Fields overwritten by [`egui::Style`] are: + /// - [`TabInteractionStyle::outline_color`] + /// - [`TabInteractionStyle::bg_fill`] + /// - [`TabInteractionStyle::text_color`] + /// - [`TabInteractionStyle::rounding`] + pub fn from_egui_active_with_kb_focus(style: &egui::Style) -> Self { + Self { + text_color: style.visuals.strong_text_color(), + outline_color: style.visuals.widgets.hovered.bg_stroke.color, + ..TabInteractionStyle::from_egui_active(style) + } + } + + /// Derives relevant fields from `egui::Style` for an inactive tab with keyboard focus and sets the remaining fields to their default values. + /// + /// Fields overwritten by [`egui::Style`] are: + /// - [`TabInteractionStyle::outline_color`] + /// - [`TabInteractionStyle::bg_fill`] + /// - [`TabInteractionStyle::text_color`] + /// - [`TabInteractionStyle::rounding`] + pub fn from_egui_inactive_with_kb_focus(style: &egui::Style) -> Self { + Self { + text_color: style.visuals.strong_text_color(), + outline_color: style.visuals.widgets.hovered.bg_stroke.color, + ..TabInteractionStyle::from_egui_inactive(style) + } + } + + /// Derives relevant fields from `egui::Style` for a focused tab with keyboard focus and sets the remaining fields to their default values. + /// + /// Fields overwritten by [`egui::Style`] are: + /// - [`TabInteractionStyle::outline_color`] + /// - [`TabInteractionStyle::bg_fill`] + /// - [`TabInteractionStyle::text_color`] + /// - [`TabInteractionStyle::rounding`] + pub fn from_egui_focused_with_kb_focus(style: &egui::Style) -> Self { + Self { + text_color: style.visuals.strong_text_color(), + outline_color: style.visuals.widgets.hovered.bg_stroke.color, + ..TabInteractionStyle::from_egui_focused(style) + } + } } impl TabBodyStyle { diff --git a/src/widgets/dock_area/show/leaf.rs b/src/widgets/dock_area/show/leaf.rs index 9c43b77..1aedd37 100644 --- a/src/widgets/dock_area/show/leaf.rs +++ b/src/widgets/dock_area/show/leaf.rs @@ -1,9 +1,9 @@ use std::ops::RangeInclusive; use egui::{ - epaint::TextShape, lerp, pos2, vec2, Align, Align2, Button, CursorIcon, Frame, Id, LayerId, - Layout, NumExt, Order, PointerButton, Rect, Response, Rounding, ScrollArea, Sense, Stroke, - TextStyle, Ui, Vec2, WidgetText, + epaint::TextShape, lerp, pos2, vec2, Align, Align2, Button, CursorIcon, Frame, Id, Key, + LayerId, Layout, NumExt, Order, PointerButton, Rect, Response, Rounding, ScrollArea, Sense, + Stroke, TextStyle, Ui, Vec2, WidgetText, }; use crate::{ @@ -81,7 +81,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { let style = fade_style.unwrap_or_else(|| self.style.as_ref().unwrap()); let (tabbar_outer_rect, tabbar_response) = ui.allocate_exact_size( vec2(ui.available_width(), style.tab_bar.height), - Sense::click(), + Sense::hover(), ); ui.painter().rect_filled( tabbar_outer_rect, @@ -235,7 +235,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { let show_close_button = self.show_close_buttons && closeable; - let response = if is_being_dragged { + let (response, title_id) = if is_being_dragged { let layer_id = LayerId::new(Order::Tooltip, id); let response = tabs_ui .with_layer_id(layer_id, |ui| { @@ -253,6 +253,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { ) }) .response; + let title_id = response.id; let sense = Sense::click_and_drag(); let response = tabs_ui.interact(response.rect, id, sense); @@ -272,7 +273,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { } } - response + (response, title_id) } else { let (mut response, close_response) = self.tab_title( tabs_ui, @@ -286,6 +287,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { show_close_button, fade, ); + let title_id = response.id; let (close_hovered, close_clicked) = close_response .map(|res| (res.hovered(), res.clicked())) @@ -322,7 +324,8 @@ impl<'tree, Tab> DockArea<'tree, Tab> { }; let tab = &mut tabs[tab_index.0]; - response = response.context_menu(|ui| { + let response = tabs_ui.interact(response.rect, id, Sense::click()); + response.context_menu(|ui| { tab_viewer.context_menu(ui, tab, surface_index, node_index); if (surface_index.is_main() || !is_lonely_tab) && tab_viewer.allowed_in_windows(tab) @@ -374,7 +377,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { } } - response + (response, title_id) }; // Paint hline below each tab unless its active (or option says otherwise). @@ -399,7 +402,10 @@ impl<'tree, Tab> DockArea<'tree, Tab> { ); } - if response.clicked() { + if response.clicked() + || (tabs_ui.memory(|m| m.has_focus(title_id)) + && tabs_ui.input(|i| i.key_pressed(Key::Enter) || i.key_pressed(Key::Space))) + { *active = tab_index; self.new_focused = Some((surface_index, node_index)); } @@ -445,7 +451,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { response = response.on_hover_cursor(CursorIcon::PointingHand); let style = fade_style.unwrap_or_else(|| self.style.as_ref().unwrap()); - let color = if response.hovered() { + let color = if response.hovered() || response.has_focus() { ui.painter() .rect_filled(rect, Rounding::ZERO, style.buttons.add_tab_bg_fill); style.buttons.add_tab_active_color @@ -524,18 +530,30 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .at_least(text_width + close_button_size); let tab_width = prefered_width.unwrap_or(0.0).at_least(minimum_width); - let (rect, mut response) = - ui.allocate_exact_size(vec2(tab_width, ui.available_height()), Sense::hover()); + let (rect, mut response) = ui.allocate_exact_size( + vec2(tab_width, ui.available_height()), + Sense::focusable_noninteractive(), + ); if !ui.memory(|mem| mem.is_anything_being_dragged()) && self.draggable_tabs { response = response.on_hover_cursor(CursorIcon::PointingHand); } let tab_style = if focused || is_being_dragged { - &tab_style.focused + if response.has_focus() { + &tab_style.focused_with_kb_focus + } else { + &tab_style.focused + } } else if active { - &tab_style.active + if response.has_focus() { + &tab_style.active_with_kb_focus + } else { + &tab_style.active + } } else if response.hovered() { &tab_style.hovered + } else if response.has_focus() { + &tab_style.inactive_with_kb_focus } else { &tab_style.inactive }; @@ -590,13 +608,13 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .interact(close_button_rect, id, Sense::click()) .on_hover_cursor(CursorIcon::PointingHand); - let color = if response.hovered() { + let color = if response.hovered() || response.has_focus() { style.buttons.close_tab_active_color } else { style.buttons.close_tab_color }; - if response.hovered() { + if response.hovered() || response.has_focus() { let mut rounding = tab_style.rounding; rounding.nw = 0.0; rounding.sw = 0.0; diff --git a/src/widgets/dock_area/show/mod.rs b/src/widgets/dock_area/show/mod.rs index 94c661c..298d9c6 100644 --- a/src/widgets/dock_area/show/mod.rs +++ b/src/widgets/dock_area/show/mod.rs @@ -1,6 +1,6 @@ use egui::{ - CentralPanel, Color32, Context, CursorIcon, Frame, LayerId, Order, Pos2, Rect, Rounding, Sense, - Ui, Vec2, + CentralPanel, Color32, Context, CursorIcon, EventFilter, Frame, Key, LayerId, Order, Pos2, + Rect, Rounding, Sense, Ui, Vec2, }; use duplicate::duplicate; @@ -311,7 +311,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { if surface == SurfaceIndex::main() { rect = rect.expand(-style.main_surface_border_stroke.width / 2.0); } - ui.allocate_rect(rect, Sense::click()); + ui.allocate_rect(rect, Sense::hover()); if self.dock_state[surface].is_empty() { return rect; @@ -395,6 +395,30 @@ impl<'tree, Tab> DockArea<'tree, Tab> { let response = ui.allocate_rect(interact_rect, Sense::click_and_drag()) .on_hover_and_drag_cursor(paste!{ CursorIcon::[]}); + let should_respond_to_arrow_keys = ui.input(|i| i.modifiers.command || i.modifiers.shift); + + if response.has_focus() { + // Prevent the default behaviour of removing focus from the separators when the + // arrow keys are pressed + ui.memory_mut(|m| m.set_focus_lock_filter(response.id, EventFilter { arrows: should_respond_to_arrow_keys, tab: false, escape: false })); + } + + let arrow_key_offset = if response.has_focus() && should_respond_to_arrow_keys { + if ui.input(|i| i.key_pressed(Key::ArrowUp)) { + Some(egui::vec2(0., -16.)) + } else if ui.input(|i| i.key_pressed(Key::ArrowDown)) { + Some(egui::vec2(0., 16.)) + } else if ui.input(|i| i.key_pressed(Key::ArrowLeft)) { + Some(egui::vec2(-16., 0.)) + } else if ui.input(|i| i.key_pressed(Key::ArrowRight)) { + Some(egui::vec2(16., 0.)) + } else { + None + } + } else { + None + }; + let midpoint = rect.min.dim_point + rect.dim_size() * *fraction; separator.min.dim_point = map_to_pixel( midpoint - style.separator.width * 0.5, @@ -409,7 +433,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { let color = if response.dragged() { style.separator.color_dragged - } else if response.hovered() { + } else if response.hovered() || response.has_focus() { style.separator.color_hovered } else { style.separator.color_idle @@ -420,9 +444,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> { // Update 'fraction' interaction after drawing separator, // otherwise it may overlap on other separator / bodies when // shrunk fast. - if let Some(pos) = response.interact_pointer_pos() { + if let Some(pos) = response.interact_pointer_pos().or(arrow_key_offset.map(|v| separator.center() + v)) { let dim_point = pos.dim_point; - let delta = response.drag_delta().dim_point; + let delta = arrow_key_offset.unwrap_or(response.drag_delta()).dim_point; if (delta > 0. && dim_point > midpoint && dim_point < rect.max.dim_point) || (delta < 0. && dim_point < midpoint && dim_point > rect.min.dim_point) From a2758eedc8982ae48e727b143e8d7fd7addff459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20G=C4=85sior?= Date: Mon, 8 Jan 2024 18:59:00 +0000 Subject: [PATCH 3/5] Upgrade to egui 0.25 (#214) --- Cargo.toml | 4 ++-- README.md | 2 +- src/widgets/dock_area/show/leaf.rs | 11 ++--------- src/widgets/dock_area/show/mod.rs | 7 ++++++- src/widgets/dock_area/show/window_surface.rs | 12 ++++++++---- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 725ff73..616c787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,14 @@ default = [] serde = ["dep:serde", "egui/serde"] [dependencies] -egui = { version = "0.24", default-features = false } +egui = { version = "0.25", default-features = false } serde = { version = "1", optional = true, features = ["derive"] } duplicate = "1.0" paste = "1.0" [dev-dependencies] -eframe = { version = "0.24", default-features = false, features = [ +eframe = { version = "0.25", default-features = false, features = [ "default_fonts", "glow", ] } diff --git a/README.md b/README.md index d5a605b..ab4d5b1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![github](https://img.shields.io/badge/github-Adanos020/egui_dock-8da0cb?logo=github)](https://github.com/Adanos020/egui_dock) [![Crates.io](https://img.shields.io/crates/v/egui_dock)](https://crates.io/crates/egui_dock) [![docs.rs](https://img.shields.io/docsrs/egui_dock)](https://docs.rs/egui_dock/) -[![egui_version](https://img.shields.io/badge/egui-0.24-blue)](https://github.com/emilk/egui) +[![egui_version](https://img.shields.io/badge/egui-0.25-blue)](https://github.com/emilk/egui) Originally created by [@lain-dono](https://github.com/lain-dono), this library provides a docking system for `egui`. diff --git a/src/widgets/dock_area/show/leaf.rs b/src/widgets/dock_area/show/leaf.rs index 1aedd37..f074287 100644 --- a/src/widgets/dock_area/show/leaf.rs +++ b/src/widgets/dock_area/show/leaf.rs @@ -588,15 +588,8 @@ impl<'tree, Tab> DockArea<'tree, Tab> { pos - galley.size() / 2.0 }; - let override_text_color = (!galley.galley_has_color).then_some(tab_style.text_color); - - ui.painter().add(TextShape { - pos: text_pos, - galley: galley.galley, - underline: Stroke::NONE, - override_text_color, - angle: 0.0, - }); + ui.painter() + .add(TextShape::new(text_pos, galley, tab_style.text_color)); let close_response = show_close_button.then(|| { let mut close_button_rect = rect; diff --git a/src/widgets/dock_area/show/mod.rs b/src/widgets/dock_area/show/mod.rs index 298d9c6..1d31f34 100644 --- a/src/widgets/dock_area/show/mod.rs +++ b/src/widgets/dock_area/show/mod.rs @@ -400,7 +400,12 @@ impl<'tree, Tab> DockArea<'tree, Tab> { if response.has_focus() { // Prevent the default behaviour of removing focus from the separators when the // arrow keys are pressed - ui.memory_mut(|m| m.set_focus_lock_filter(response.id, EventFilter { arrows: should_respond_to_arrow_keys, tab: false, escape: false })); + ui.memory_mut(|m| m.set_focus_lock_filter(response.id, EventFilter { + horizontal_arrows: should_respond_to_arrow_keys, + vertical_arrows: should_respond_to_arrow_keys, + tab: false, + escape: false + })); } let arrow_key_offset = if response.has_focus() && should_respond_to_arrow_keys { diff --git a/src/widgets/dock_area/show/window_surface.rs b/src/widgets/dock_area/show/window_surface.rs index e18abd1..edf4d35 100644 --- a/src/widgets/dock_area/show/window_surface.rs +++ b/src/widgets/dock_area/show/window_surface.rs @@ -80,7 +80,6 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .title(&mut tabs[active.0]) .color(ui.visuals().widgets.noninteractive.fg_stroke.color) .into_galley(ui, Some(false), 0.0, TextStyle::Button) - .galley }; // Fade window frame (if neccesary) @@ -152,8 +151,11 @@ impl<'tree, Tab> DockArea<'tree, Tab> { ch_response.header_response.rect.height(), ), ); - ui.painter() - .galley(rect.center() - (title.size() * 0.5), title); + ui.painter().galley( + rect.center() - (title.size() * 0.5), + title, + ui.visuals().widgets.noninteractive.fg_stroke.color, + ); } Some(ch_response) } else { @@ -177,7 +179,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { let rect = { let (top_right, height) = match collapse_response { Some(collapse) => ( - egui::Rect::from_two_pos( + Rect::from_two_pos( collapse.header_response.rect.right_top(), ui.max_rect().right_top(), ) @@ -199,9 +201,11 @@ impl<'tree, Tab> DockArea<'tree, Tab> { }); } } + fn min_window_width(title: &Galley, button_width: f32) -> f32 { (button_width * 2.) + title.size().x } + fn close_button(disabled: Option<&'static str>) -> impl Widget { move |ui: &mut Ui| -> Response { let sense = disabled.map_or(Sense::click(), |_disabled| Sense::hover()); From 2b7469a1de917d207b07ef4ebcf1f51c50291d3b Mon Sep 17 00:00:00 2001 From: Adanos020 Date: Mon, 8 Jan 2024 21:07:11 +0000 Subject: [PATCH 4/5] Update readme and changelog --- CHANGELOG.md | 5 ++++- README.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b312d3..e014f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # egui_dock changelog -## 0.10.0 - TBD +## 0.10.0 - 2024-01-08 ### Added From ([#211](https://github.com/Adanos020/egui_dock/pull/211)): @@ -11,6 +11,9 @@ From ([#211](https://github.com/Adanos020/egui_dock/pull/211)): ### Fixed - Widgets inside tabs are now focusable with the tab key on the keyboard. ([#211](https://github.com/Adanos020/egui_dock/pull/211)) +### Breaking changes +- Upgraded to egui 0.25 + ## 0.9.1 - 2023-12-10 ### Fixed diff --git a/README.md b/README.md index ab4d5b1..6afa8a3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Add `egui` and `egui_dock` to your project's dependencies. ```toml [dependencies] -egui = "0.24" +egui = "0.25" egui_dock = "0.10" ``` From bfd589a582f8d94b15edd59ab5f3a6f8d77fdbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20G=C4=85sior?= Date: Tue, 9 Jan 2024 11:51:01 +0000 Subject: [PATCH 5/5] Add window tooltip translation (#216) * Add translation for window close button tooltip * Rename `default` to `english` in `{TabContextMenu,Window,}Translations` * Update changelog --- CHANGELOG.md | 4 +- src/dock_state/mod.rs | 2 +- src/dock_state/translations.rs | 31 +++++++++-- src/lib.rs | 6 ++- src/widgets/dock_area/show/leaf.rs | 11 ++-- src/widgets/dock_area/show/window_surface.rs | 54 +++++++++++--------- 6 files changed, 70 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e014f74..199bf57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,16 +3,18 @@ ## 0.10.0 - 2024-01-08 ### Added -From ([#211](https://github.com/Adanos020/egui_dock/pull/211)): +- From ([#211](https://github.com/Adanos020/egui_dock/pull/211)): - Tabs, the close tab buttons and the add tab buttons are now focusable with the keyboard and interactable with the enter key and space bar. - Separators are now focusable with the keyboard and movable using the arrow keys while control or shift is held. - `TabStyle::active_with_kb_focus`, `TabStyle::inactive_with_kb_focus` and `TabStyle::focused_with_kb_focus` for style of tabs that are focused with the keyboard. +- Missing translation for the tooltip showing when you hover on a grayed out window close button. ([#216](https://github.com/Adanos020/egui_dock/pull/216)) ### Fixed - Widgets inside tabs are now focusable with the tab key on the keyboard. ([#211](https://github.com/Adanos020/egui_dock/pull/211)) ### Breaking changes - Upgraded to egui 0.25 +- Replaced `Default` implementations for `{TabContextMenu,Window,}Translations` with associated functions called `english`. ([#216](https://github.com/Adanos020/egui_dock/pull/216)) ## 0.9.1 - 2023-12-10 diff --git a/src/dock_state/mod.rs b/src/dock_state/mod.rs index 07e3888..e95024f 100644 --- a/src/dock_state/mod.rs +++ b/src/dock_state/mod.rs @@ -67,7 +67,7 @@ impl DockState { Self { surfaces: vec![Surface::Main(Tree::new(tabs))], focused_surface: None, - translations: Translations::default(), + translations: Translations::english(), } } diff --git a/src/dock_state/translations.rs b/src/dock_state/translations.rs index 54a0041..0ff648e 100644 --- a/src/dock_state/translations.rs +++ b/src/dock_state/translations.rs @@ -4,6 +4,8 @@ pub struct Translations { /// Text overrides for buttons in tab context menus. pub tab_context_menu: TabContextMenuTranslations, + /// Text overrides for buttons in windows. + pub window: WindowTranslations, } /// Specifies text in buttons displayed in the context menu displayed upon right-clicking on a tab. @@ -16,21 +18,40 @@ pub struct TabContextMenuTranslations { pub eject_button: String, } -impl Default for Translations { +/// Specifies text in buttons displayed in the context menu displayed upon right-clicking on a tab. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct WindowTranslations { + /// Message in the tooltip shown while hovering over a grayed out X button of a window + /// containing non-closable tabs. + pub close_button_tooltip: String, +} + +impl Translations { /// Default English translations. - fn default() -> Self { + pub fn english() -> Self { Self { - tab_context_menu: TabContextMenuTranslations::default(), + tab_context_menu: TabContextMenuTranslations::english(), + window: WindowTranslations::english(), } } } -impl Default for TabContextMenuTranslations { +impl TabContextMenuTranslations { /// Default English translations. - fn default() -> Self { + pub fn english() -> Self { Self { close_button: String::from("Close"), eject_button: String::from("Eject"), } } } + +impl WindowTranslations { + /// Default English translations. + pub fn english() -> Self { + Self { + close_button_tooltip: String::from("This window contains non-closable tabs."), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d59ca37..569b5f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,13 +192,16 @@ //! Example usage: //! //! ```rust -//! # use egui_dock::{DockState, TabContextMenuTranslations, Translations}; +//! # use egui_dock::{DockState, TabContextMenuTranslations, Translations, WindowTranslations}; //! # type Tab = (); //! let translations_pl = Translations { //! tab_context_menu: TabContextMenuTranslations { //! close_button: "Zamknij zakładkę".to_string(), //! eject_button: "Przenieś zakładkę do nowego okna".to_string(), //! }, +//! window: WindowTranslations { +//! close_button_tooltip: "To okno zawiera zakładki, których nie można zamknąć.".to_string(), +//! } //! }; //! let dock_state = DockState::::new(vec![]).with_translations(translations_pl); //! @@ -206,6 +209,7 @@ //! let mut dock_state = DockState::::new(vec![]); //! dock_state.translations.tab_context_menu.close_button = "タブを閉じる".to_string(); //! dock_state.translations.tab_context_menu.eject_button = "タブを新しいウィンドウへ移動".to_string(); +//! dock_state.translations.window.close_button_tooltip = "このウィンドウは閉じられないタブがある。".to_string(); //! ``` #![warn(missing_docs)] diff --git a/src/widgets/dock_area/show/leaf.rs b/src/widgets/dock_area/show/leaf.rs index f074287..6b5dc8e 100644 --- a/src/widgets/dock_area/show/leaf.rs +++ b/src/widgets/dock_area/show/leaf.rs @@ -386,10 +386,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { unreachable!() }; let tab = &mut tabs[tab_index.0]; - let style = match fade { - Some(fade) => fade, - None => self.style.as_ref().unwrap(), - }; + let style = fade.unwrap_or_else(|| self.style.as_ref().unwrap()); let tab_style = tab_viewer.tab_style_override(tab, &style.tab); let tab_style = tab_style.as_ref().unwrap_or(&style.tab); @@ -815,13 +812,13 @@ impl<'tree, Tab> DockArea<'tree, Tab> { }); } - //change hover destination + // change hover destination if let Some(pointer) = state.last_hover_pos { // Prevent borrow checker issues. let rect = rect.to_owned(); - //if the dragged tab isn't allowed in a window, - //it's unneccesary to change the hover state + // if the dragged tab isn't allowed in a window, + // it's unnecessary to change the hover state let is_dragged_valid = match &state.dnd { Some(DragDropState { drag: DragData { src, .. }, diff --git a/src/widgets/dock_area/show/window_surface.rs b/src/widgets/dock_area/show/window_surface.rs index edf4d35..d844c55 100644 --- a/src/widgets/dock_area/show/window_surface.rs +++ b/src/widgets/dock_area/show/window_surface.rs @@ -43,24 +43,6 @@ impl<'tree, Tab> DockArea<'tree, Tab> { None => (1.0, None), }; - // Finds out if theres a reason for the close button to be disabled - // by iterating over the tree and finding if theres any non-closable nodes. - let disabled = (!self.dock_state[surf_index] - .iter_mut() - .filter_map(|node| { - if let Node::Leaf { tabs, .. } = node { - Some( - tabs.iter_mut() - .map(|tab| tab_viewer.closeable(tab)) - .all(identity), - ) - } else { - None - } - }) - .all(identity)) - .then_some("This window contains non-closable tabs."); - // Get galley of currently selected node as a window title let title = { let node_id = self.dock_state[surf_index] @@ -82,7 +64,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .into_galley(ui, Some(false), 0.0, TextStyle::Button) }; - // Fade window frame (if neccesary) + // Fade window frame (if necessary) let mut frame = Frame::window(ui.style()); if fade_factor != 1.0 { frame.fill = frame.fill.linear_multiply(fade_factor); @@ -94,7 +76,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .frame(frame) .min_width(min_window_width(&title, ui.spacing().indent)) .show(ui.ctx(), |ui| { - //fade inner ui (if neccesary) + //fade inner ui (if necessary) if fade_factor != 1.0 { fade_visuals(ui.visuals_mut(), fade_factor); } @@ -112,6 +94,23 @@ impl<'tree, Tab> DockArea<'tree, Tab> { title, ); if self.show_window_close_buttons { + // Finds out if theres a reason for the close button to be disabled + // by iterating over the tree and finding if theres any non-closable nodes. + let disabled = !self.dock_state[surf_index] + .iter_mut() + .filter_map(|node| { + if let Node::Leaf { tabs, .. } = node { + Some( + tabs.iter_mut() + .map(|tab| tab_viewer.closeable(tab)) + .all(identity), + ) + } else { + None + } + }) + .all(identity); + self.show_close_button(ui, &mut open, ch_res, disabled); } }); @@ -174,7 +173,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { ui: &mut Ui, open: &mut bool, collapse_response: Option>, - disabled: Option<&'static str>, + disabled: bool, ) { let rect = { let (top_right, height) = match collapse_response { @@ -191,10 +190,19 @@ impl<'tree, Tab> DockArea<'tree, Tab> { Rect::from_min_size(top_right, Vec2::new(0.0, height)) }; + let close_button = close_button( + disabled.then_some( + self.dock_state + .translations + .window + .close_button_tooltip + .as_str(), + ), + ); ui.allocate_ui_at_rect(rect, |ui| { ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| { ui.set_height(rect.height()); - if ui.add(close_button(disabled)).clicked() { + if ui.add(close_button).clicked() { *open = false; } }); @@ -206,7 +214,7 @@ fn min_window_width(title: &Galley, button_width: f32) -> f32 { (button_width * 2.) + title.size().x } -fn close_button(disabled: Option<&'static str>) -> impl Widget { +fn close_button(disabled: Option<&str>) -> impl Widget + '_ { move |ui: &mut Ui| -> Response { let sense = disabled.map_or(Sense::click(), |_disabled| Sense::hover());