servoshell: Add close buttons and increase interactivity of tabs (#33244)

* Improved the minibrowser tab bar

Added a close button for each tab as well as another button for opening
a new tab, also changed the styling so it looks more like other
browsers.

Signed-off-by: Benjamin Vincent Schulenburg <bennyschulenburg@gmx.de>

* Make sure to restore the egui visuals after drawing a browser tab

Signed-off-by: Benjamin Vincent Schulenburg <bennyschulenburg@gmx.de>

* Only use colors from the current theme for the minibrowser tabbar

That way we can easily switch between light and dark mode

Signed-off-by: Benjamin Vincent Schulenburg <bennyschulenburg@gmx.de>

---------

Signed-off-by: Benjamin Vincent Schulenburg <bennyschulenburg@gmx.de>
This commit is contained in:
Ben 2024-09-04 11:31:23 +00:00 committed by GitHub
parent 3c6ca33832
commit 891562be8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -10,8 +10,8 @@ use std::time::Instant;
use egui::text::{CCursor, CCursorRange}; use egui::text::{CCursor, CCursorRange};
use egui::text_edit::TextEditState; use egui::text_edit::TextEditState;
use egui::{ use egui::{
pos2, CentralPanel, Color32, Frame, Key, Label, Modifiers, PaintCallback, Pos2, pos2, CentralPanel, Frame, Key, Label, Modifiers, PaintCallback, Pos2, SelectableLabel,
SelectableLabel, TopBottomPanel, Vec2, TopBottomPanel, Vec2,
}; };
use egui_glow::CallbackFn; use egui_glow::CallbackFn;
use egui_winit::EventResponse; use egui_winit::EventResponse;
@ -19,12 +19,14 @@ use euclid::{Box2D, Length, Point2D, Scale, Size2D};
use gleam::gl; use gleam::gl;
use glow::NativeFramebuffer; use glow::NativeFramebuffer;
use log::{trace, warn}; use log::{trace, warn};
use servo::base::id::WebViewId;
use servo::compositing::windowing::EmbedderEvent; use servo::compositing::windowing::EmbedderEvent;
use servo::script_traits::TraversalDirection; use servo::script_traits::TraversalDirection;
use servo::servo_geometry::DeviceIndependentPixel; use servo::servo_geometry::DeviceIndependentPixel;
use servo::servo_url::ServoUrl; use servo::servo_url::ServoUrl;
use servo::style_traits::DevicePixel; use servo::style_traits::DevicePixel;
use servo::webrender_traits::RenderingContext; use servo::webrender_traits::RenderingContext;
use servo::TopLevelBrowsingContextId;
use winit::event::{ElementState, MouseButton}; use winit::event::{ElementState, MouseButton};
use super::egui_glue::EguiGlow; use super::egui_glue::EguiGlow;
@ -61,6 +63,7 @@ pub enum MinibrowserEvent {
Back, Back,
Forward, Forward,
Reload, Reload,
NewWebView,
} }
fn truncate_with_ellipsis(input: &str, max_length: usize) -> String { fn truncate_with_ellipsis(input: &str, max_length: usize) -> String {
@ -163,6 +166,87 @@ impl Minibrowser {
.min_size(Vec2 { x: 20.0, y: 20.0 }) .min_size(Vec2 { x: 20.0, y: 20.0 })
} }
/// Draws a browser tab, checking for clicks and returns an appropriate [EmbedderEvent]
/// Using a custom widget here would've been nice, but it doesn't seem as though egui
/// supports that, so we arrange multiple Widgets in a way that they look connected.
fn browser_tab(
ui: &mut egui::Ui,
label: &str,
selected: bool,
webview_id: TopLevelBrowsingContextId,
) -> Option<EmbedderEvent> {
let old_item_spacing = ui.spacing().item_spacing;
let old_visuals = ui.visuals().clone();
let active_bg_color = old_visuals.widgets.active.weak_bg_fill;
let inactive_bg_color = old_visuals.window_fill;
ui.spacing_mut().item_spacing = egui::vec2(0.0, 0.0);
let visuals = ui.visuals_mut();
// Remove the stroke so we don't see the border between the close button and the label
visuals.widgets.active.bg_stroke.width = 0.0;
visuals.widgets.hovered.bg_stroke.width = 0.0;
// Now we make sure the fill color is always the same, irrespective of state, that way
// we can make sure that both the label and close button have the same background color
visuals.widgets.noninteractive.weak_bg_fill = inactive_bg_color;
visuals.widgets.inactive.weak_bg_fill = inactive_bg_color;
visuals.widgets.hovered.weak_bg_fill = active_bg_color;
visuals.widgets.active.weak_bg_fill = active_bg_color;
visuals.selection.bg_fill = active_bg_color;
visuals.selection.stroke.color = visuals.widgets.active.fg_stroke.color;
visuals.widgets.hovered.fg_stroke.color = visuals.widgets.active.fg_stroke.color;
// Expansion would also show that they are 2 separate widgets
visuals.widgets.active.expansion = 0.0;
visuals.widgets.hovered.expansion = 0.0;
// The rounding is changed so it looks as though the 2 widgets are a single widget
// with a uniform rounding
let rounding = egui::Rounding {
ne: 0.0,
nw: 4.0,
sw: 4.0,
se: 0.0,
};
visuals.widgets.active.rounding = rounding;
visuals.widgets.hovered.rounding = rounding;
visuals.widgets.inactive.rounding = rounding;
let tab = ui.add(SelectableLabel::new(
selected,
truncate_with_ellipsis(label, 20),
));
let tab = tab.on_hover_ui(|ui| {
ui.label(label);
});
let rounding = egui::Rounding {
ne: 4.0,
nw: 0.0,
sw: 0.0,
se: 4.0,
};
let visuals = ui.visuals_mut();
visuals.widgets.active.rounding = rounding;
visuals.widgets.hovered.rounding = rounding;
visuals.widgets.inactive.rounding = rounding;
let fill_color = if selected || tab.hovered() {
active_bg_color
} else {
inactive_bg_color
};
ui.spacing_mut().item_spacing = old_item_spacing;
let close_button = ui.add(egui::Button::new("X").fill(fill_color));
*ui.visuals_mut() = old_visuals;
if close_button.clicked() || close_button.middle_clicked() || tab.middle_clicked() {
Some(EmbedderEvent::CloseWebView(webview_id))
} else if !selected && tab.clicked() {
Some(EmbedderEvent::FocusWebView(webview_id))
} else {
None
}
}
/// Update the minibrowser, but dont paint. /// Update the minibrowser, but dont paint.
/// If `servo_framebuffer_id` is given, set up a paint callback to blit its contents to our /// If `servo_framebuffer_id` is given, set up a paint callback to blit its contents to our
/// CentralPanel when [`Minibrowser::paint`] is called. /// CentralPanel when [`Minibrowser::paint`] is called.
@ -195,7 +279,7 @@ impl Minibrowser {
// when not displaying the URL bar: https://github.com/servo/servo/issues/32443 // when not displaying the URL bar: https://github.com/servo/servo/issues/32443
if window.fullscreen().is_none() { if window.fullscreen().is_none() {
let frame = egui::Frame::default() let frame = egui::Frame::default()
.fill(Color32::from_gray(32)) .fill(ctx.style().visuals.window_fill)
.inner_margin(4.0); .inner_margin(4.0);
TopBottomPanel::top("toolbar").frame(frame).show(ctx, |ui| { TopBottomPanel::top("toolbar").frame(frame).show(ctx, |ui| {
ui.allocate_ui_with_layout( ui.allocate_ui_with_layout(
@ -267,30 +351,27 @@ impl Minibrowser {
let mut embedder_events = vec![]; let mut embedder_events = vec![];
// A simple Tab header strip, using egui 'SelectableLabel' elements. // A simple Tab header strip
// TODO: Add a way to close a tab eg. with a [x] control.
TopBottomPanel::top("tabs").show(ctx, |ui| { TopBottomPanel::top("tabs").show(ctx, |ui| {
ui.allocate_ui_with_layout( ui.allocate_ui_with_layout(
ui.available_size(), ui.available_size(),
egui::Layout::left_to_right(egui::Align::Center), egui::Layout::left_to_right(egui::Align::Center),
|ui| { |ui| {
for (webview_id, webview) in webviews.webviews().into_iter() { for (webview_id, webview) in webviews.webviews().into_iter() {
let msg = match (&webview.title, &webview.url) { let label = match (&webview.title, &webview.url) {
(Some(title), _) if !title.is_empty() => title.clone(), (Some(title), _) => title,
(_, Some(url)) => url.to_string(), (None, Some(url)) => &url.to_string(),
_ => "New Tab".to_owned(), _ => "New Tab",
}; };
let tab = ui.add(SelectableLabel::new( if let Some(event) =
webview.focused, Self::browser_tab(ui, label, webview.focused, webview_id)
truncate_with_ellipsis(&msg, 20), {
)); embedder_events.push(event);
let tab = tab.on_hover_ui(|ui| {
ui.label(&msg);
});
if !webview.focused && tab.clicked() {
embedder_events.push(EmbedderEvent::FocusWebView(webview_id));
} }
} }
if ui.add(Minibrowser::toolbar_button("+")).clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::NewWebView);
}
}, },
); );
}); });
@ -441,6 +522,10 @@ impl Minibrowser {
let browser_id = browser.focused_webview_id().unwrap(); let browser_id = browser.focused_webview_id().unwrap();
app_event_queue.push(EmbedderEvent::Reload(browser_id)); app_event_queue.push(EmbedderEvent::Reload(browser_id));
}, },
MinibrowserEvent::NewWebView => {
let url = ServoUrl::parse("servo:newtab").unwrap();
app_event_queue.push(EmbedderEvent::NewWebView(url, WebViewId::new()));
},
} }
} }
} }