views: Add support for absolute positioned overlay views.

To test:
Run `cefclient.exe --use-views --hide-frame --hide-controls`
Add `--enable-chrome-runtime` for the same behavior using the Chrome location
bar instead of a text field.
This commit is contained in:
Marshall Greenblatt
2021-08-27 21:55:15 -04:00
parent 6f6072b857
commit 4a44e16a09
96 changed files with 3875 additions and 230 deletions

View File

@@ -9,6 +9,7 @@
#include "include/base/cef_build.h"
#include "include/base/cef_callback.h"
#include "include/cef_app.h"
#include "include/cef_i18n_util.h"
#include "include/views/cef_box_layout.h"
#include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/main_context.h"
@@ -190,12 +191,12 @@ void ViewsWindow::Close(bool force) {
void ViewsWindow::SetAddress(const std::string& url) {
CEF_REQUIRE_UI_THREAD();
if (!window_ || !with_controls_)
if (!window_)
return;
CefRefPtr<CefView> view = window_->GetViewForID(ID_URL_TEXTFIELD);
if (view && view->AsTextfield())
view->AsTextfield()->SetText(url);
// |location_bar_| may instead be a Chrome toolbar.
if (location_bar_ && location_bar_->AsTextfield())
location_bar_->AsTextfield()->SetText(url);
}
void ViewsWindow::SetTitle(const std::string& title) {
@@ -236,14 +237,18 @@ void ViewsWindow::SetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) {
CEF_REQUIRE_UI_THREAD();
if (!window_ || !with_controls_ || chrome_toolbar_type_ == CEF_CTT_NORMAL)
if (!window_ || chrome_toolbar_type_ == CEF_CTT_NORMAL)
return;
EnableView(ID_BACK_BUTTON, canGoBack);
EnableView(ID_FORWARD_BUTTON, canGoForward);
EnableView(ID_RELOAD_BUTTON, !isLoading);
EnableView(ID_STOP_BUTTON, isLoading);
EnableView(ID_URL_TEXTFIELD, true);
if (with_controls_) {
EnableView(ID_BACK_BUTTON, canGoBack);
EnableView(ID_FORWARD_BUTTON, canGoForward);
EnableView(ID_RELOAD_BUTTON, !isLoading);
EnableView(ID_STOP_BUTTON, isLoading);
}
if (location_bar_) {
EnableView(ID_URL_TEXTFIELD, true);
}
}
void ViewsWindow::SetDraggableRegions(
@@ -266,20 +271,25 @@ void ViewsWindow::SetDraggableRegions(
window_regions.push_back(region);
}
if (overlay_controls_) {
// Exclude all regions obscured by overlays.
overlay_controls_->UpdateDraggableRegions(window_regions);
}
window_->SetDraggableRegions(window_regions);
}
void ViewsWindow::TakeFocus(bool next) {
CEF_REQUIRE_UI_THREAD();
if (!window_ || !with_controls_)
if (!window_)
return;
if (chrome_toolbar_type_ == CEF_CTT_NORMAL) {
top_toolbar_->RequestFocus();
} else {
// Give focus to the URL textfield.
location_->RequestFocus();
} else if (location_bar_) {
// Give focus to the location bar.
location_bar_->RequestFocus();
}
}
@@ -427,18 +437,30 @@ void ViewsWindow::OnMenuButtonPressed(
return;
}
DCHECK(with_controls_);
DCHECK(with_controls_ || with_overlay_controls_);
DCHECK_EQ(ID_MENU_BUTTON, id);
menu_button->ShowMenu(button_menu_model_, screen_point,
CEF_MENU_ANCHOR_TOPRIGHT);
auto point = screen_point;
if (with_overlay_controls_) {
// Align the menu correctly under the button.
const int button_width = menu_button->GetSize().width;
if (CefIsRTL()) {
point.x += button_width - 4;
} else {
point.x -= button_width - 4;
}
}
menu_button->ShowMenu(button_menu_model_, point,
with_overlay_controls_ ? CEF_MENU_ANCHOR_TOPLEFT
: CEF_MENU_ANCHOR_TOPRIGHT);
}
void ViewsWindow::ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
int command_id,
cef_event_flags_t event_flags) {
CEF_REQUIRE_UI_THREAD();
DCHECK(with_controls_);
DCHECK(with_controls_ || with_overlay_controls_);
if (command_id == ID_QUIT) {
delegate_->OnExit();
@@ -452,7 +474,6 @@ void ViewsWindow::ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
bool ViewsWindow::OnKeyEvent(CefRefPtr<CefTextfield> textfield,
const CefKeyEvent& event) {
CEF_REQUIRE_UI_THREAD();
DCHECK(with_controls_);
DCHECK_EQ(ID_URL_TEXTFIELD, textfield->GetID());
// Trigger when the return key is pressed.
@@ -460,12 +481,9 @@ bool ViewsWindow::OnKeyEvent(CefRefPtr<CefTextfield> textfield,
event.windows_key_code == VK_RETURN) {
CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
if (browser) {
CefRefPtr<CefView> view = window_->GetViewForID(ID_URL_TEXTFIELD);
if (view && view->AsTextfield()) {
const CefString& url = view->AsTextfield()->GetText();
if (!url.empty())
browser->GetMainFrame()->LoadURL(url);
}
const CefString& url = textfield->GetText();
if (!url.empty())
browser->GetMainFrame()->LoadURL(url);
}
// We handled the event.
@@ -500,9 +518,14 @@ void ViewsWindow::OnWindowCreated(CefRefPtr<CefWindow> window) {
// Set the background color for regions that are not obscured by other Views.
views_style::ApplyTo(window_.get());
if (with_controls_ || with_overlay_controls_) {
// Create the MenuModel that will be displayed via the menu button.
CreateMenuModel();
}
if (with_controls_) {
// Add the BrowserView and other controls to the Window.
AddControls();
AddBrowserView();
// Add keyboard accelerators to the Window.
AddAccelerators();
@@ -535,6 +558,7 @@ void ViewsWindow::OnWindowDestroyed(CefRefPtr<CefWindow> window) {
top_menu_bar_ = nullptr;
}
extensions_panel_ = nullptr;
menu_button_ = nullptr;
window_ = nullptr;
}
@@ -681,117 +705,41 @@ void ViewsWindow::OnBlur(CefRefPtr<CefView> view) {
}
void ViewsWindow::OnWindowChanged(CefRefPtr<CefView> view, bool added) {
if (!with_controls_ || !added)
return;
const int view_id = view->GetID();
if (view_id != ID_BROWSER_VIEW)
return;
// Build the remainder of the UI now that the BrowserView has been added to
// the CefWindow. This is a requirement to use Chrome toolbars.
CefRefPtr<CefPanel> top_menu_panel;
if (top_menu_bar_)
top_menu_panel = top_menu_bar_->GetMenuPanel();
LabelButtons browse_buttons;
CefRefPtr<CefMenuButton> menu_button;
if (chrome_toolbar_type_ == CEF_CTT_NORMAL) {
// Chrome will provide a normal toolbar with location, menu, etc.
top_toolbar_ = browser_view_->GetChromeToolbar();
DCHECK(top_toolbar_);
}
if (!top_toolbar_) {
// Create the browse buttons.
browse_buttons.push_back(CreateBrowseButton("Back", ID_BACK_BUTTON));
browse_buttons.push_back(CreateBrowseButton("Forward", ID_FORWARD_BUTTON));
browse_buttons.push_back(CreateBrowseButton("Reload", ID_RELOAD_BUTTON));
browse_buttons.push_back(CreateBrowseButton("Stop", ID_STOP_BUTTON));
if (chrome_toolbar_type_ == CEF_CTT_LOCATION) {
// Chrome will provide a minimal location bar.
location_ = browser_view_->GetChromeToolbar();
DCHECK(location_);
}
if (!location_) {
// Create the URL textfield.
CefRefPtr<CefTextfield> url_textfield =
CefTextfield::CreateTextfield(this);
url_textfield->SetID(ID_URL_TEXTFIELD);
url_textfield->SetEnabled(false); // Disabled by default.
views_style::ApplyTo(url_textfield);
location_ = url_textfield;
if (added) {
if (with_controls_) {
AddControls();
}
// Create the menu button.
menu_button = CefMenuButton::CreateMenuButton(this, CefString());
menu_button->SetID(ID_MENU_BUTTON);
menu_button->SetImage(
CEF_BUTTON_STATE_NORMAL,
delegate_->GetImageCache()->GetCachedImage("menu_icon"));
views_style::ApplyTo(menu_button.get());
menu_button->SetInkDropEnabled(true);
// Override the default minimum size.
menu_button->SetMinimumSize(CefSize(0, 0));
// Create the top panel.
CefRefPtr<CefPanel> top_panel = CefPanel::CreatePanel(nullptr);
// Use a horizontal box layout for |top_panel|.
CefBoxLayoutSettings top_panel_layout_settings;
top_panel_layout_settings.horizontal = true;
CefRefPtr<CefBoxLayout> top_panel_layout =
top_panel->SetToBoxLayout(top_panel_layout_settings);
// Add the buttons and URL textfield to |top_panel|.
for (size_t i = 0U; i < browse_buttons.size(); ++i)
top_panel->AddChildView(browse_buttons[i]);
top_panel->AddChildView(location_);
UpdateExtensionControls();
DCHECK(extensions_panel_);
top_panel->AddChildView(extensions_panel_);
top_panel->AddChildView(menu_button);
views_style::ApplyTo(top_panel);
// Allow |location| to grow and fill any remaining space.
top_panel_layout->SetFlexForView(location_, 1);
top_toolbar_ = top_panel;
if (with_overlay_controls_) {
overlay_controls_ = new ViewsOverlayControls();
overlay_controls_->Initialize(window_, CreateMenuButton(),
CreateLocationBar(),
chrome_toolbar_type_ != CEF_CTT_NONE);
}
} else {
if (overlay_controls_) {
// Overlay controls may include the Chrome toolbar, in which case they
// need to be removed before the BrowserView.
overlay_controls_->Destroy();
overlay_controls_ = nullptr;
location_bar_ = nullptr;
}
}
}
// Add the top panel and browser view to |window|.
int top_index = 0;
if (top_menu_panel)
window_->AddChildViewAt(top_menu_panel, top_index++);
window_->AddChildViewAt(top_toolbar_, top_index);
void ViewsWindow::OnLayoutChanged(CefRefPtr<CefView> view,
const CefRect& new_bounds) {
const int view_id = view->GetID();
if (view_id != ID_BROWSER_VIEW)
return;
// Lay out |window| so we can get the default button sizes.
window_->Layout();
int min_width = 200;
if (!browse_buttons.empty()) {
// Make all browse buttons the same size.
MakeButtonsSameSize(browse_buttons);
// Lay out |window| again with the new button sizes.
window_->Layout();
// Minimum window width is the size of all buttons plus some extra.
min_width = browse_buttons[0]->GetBounds().width * 4 +
menu_button->GetBounds().width + 100;
if (overlay_controls_) {
overlay_controls_->UpdateControls();
}
// Minimum window height is the hight of the top toolbar plus some extra.
int min_height = top_toolbar_->GetBounds().height + 100;
if (top_menu_panel)
min_height += top_menu_panel->GetBounds().height;
minimum_window_size_ = CefSize(min_width, min_height);
}
void ViewsWindow::MenuBarExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
@@ -812,8 +760,16 @@ ViewsWindow::ViewsWindow(Delegate* delegate,
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
frameless_ = command_line->HasSwitch(switches::kHideFrame) ||
delegate_->WithExtension();
const bool hide_frame = command_line->HasSwitch(switches::kHideFrame);
const bool hide_overlays = command_line->HasSwitch(switches::kHideOverlays);
// Without a window frame.
frameless_ = hide_frame || delegate_->WithExtension();
// With an overlay that mimics window controls.
with_overlay_controls_ =
hide_frame && !hide_overlays && !delegate_->WithControls();
if (MainContext::Get()->UseChromeRuntime()) {
const std::string& toolbar_type =
@@ -823,7 +779,8 @@ ViewsWindow::ViewsWindow(Delegate* delegate,
} else if (toolbar_type == "location") {
chrome_toolbar_type_ = CEF_CTT_LOCATION;
} else {
chrome_toolbar_type_ = CEF_CTT_NORMAL;
chrome_toolbar_type_ =
with_overlay_controls_ ? CEF_CTT_LOCATION : CEF_CTT_NORMAL;
}
} else {
chrome_toolbar_type_ = CEF_CTT_NONE;
@@ -869,16 +826,49 @@ CefRefPtr<CefLabelButton> ViewsWindow::CreateBrowseButton(
CefRefPtr<CefLabelButton> button =
CefLabelButton::CreateLabelButton(this, label);
button->SetID(id);
views_style::ApplyTo(button.get());
button->SetInkDropEnabled(true);
button->SetEnabled(false); // Disabled by default.
button->SetFocusable(false); // Don't give focus to the button.
return button;
}
void ViewsWindow::AddControls() {
// Create the MenuModel that will be displayed via the menu button.
CreateMenuModel();
CefRefPtr<CefMenuButton> ViewsWindow::CreateMenuButton() {
// Create the menu button.
DCHECK(!menu_button_);
menu_button_ = CefMenuButton::CreateMenuButton(this, CefString());
menu_button_->SetID(ID_MENU_BUTTON);
menu_button_->SetImage(
CEF_BUTTON_STATE_NORMAL,
delegate_->GetImageCache()->GetCachedImage("menu_icon"));
views_style::ApplyTo(menu_button_.get());
menu_button_->SetInkDropEnabled(true);
// Override the default minimum size.
menu_button_->SetMinimumSize(CefSize(0, 0));
return menu_button_;
}
CefRefPtr<CefView> ViewsWindow::CreateLocationBar() {
DCHECK(!location_bar_);
if (chrome_toolbar_type_ == CEF_CTT_LOCATION) {
// Chrome will provide a minimal location bar.
location_bar_ = browser_view_->GetChromeToolbar();
DCHECK(location_bar_);
views_style::ApplyBackgroundTo(location_bar_);
}
if (!location_bar_) {
// Create the URL textfield.
CefRefPtr<CefTextfield> url_textfield = CefTextfield::CreateTextfield(this);
url_textfield->SetID(ID_URL_TEXTFIELD);
url_textfield->SetEnabled(false); // Disabled by default.
views_style::ApplyTo(url_textfield);
location_bar_ = url_textfield;
}
return location_bar_;
}
void ViewsWindow::AddBrowserView() {
// Use a vertical box layout for |window|.
CefBoxLayoutSettings window_layout_settings;
window_layout_settings.horizontal = false;
@@ -896,6 +886,89 @@ void ViewsWindow::AddControls() {
// only available after the BrowserView is added.
}
void ViewsWindow::AddControls() {
// Build the remainder of the UI now that the BrowserView has been added to
// the CefWindow. This is a requirement to use Chrome toolbars.
CefRefPtr<CefPanel> top_menu_panel;
if (top_menu_bar_)
top_menu_panel = top_menu_bar_->GetMenuPanel();
LabelButtons browse_buttons;
if (chrome_toolbar_type_ == CEF_CTT_NORMAL) {
// Chrome will provide a normal toolbar with location, menu, etc.
top_toolbar_ = browser_view_->GetChromeToolbar();
DCHECK(top_toolbar_);
}
if (!top_toolbar_) {
// Create the browse buttons.
browse_buttons.push_back(CreateBrowseButton("Back", ID_BACK_BUTTON));
browse_buttons.push_back(CreateBrowseButton("Forward", ID_FORWARD_BUTTON));
browse_buttons.push_back(CreateBrowseButton("Reload", ID_RELOAD_BUTTON));
browse_buttons.push_back(CreateBrowseButton("Stop", ID_STOP_BUTTON));
CreateLocationBar();
CreateMenuButton();
// Create the top panel.
CefRefPtr<CefPanel> top_panel = CefPanel::CreatePanel(nullptr);
// Use a horizontal box layout for |top_panel|.
CefBoxLayoutSettings top_panel_layout_settings;
top_panel_layout_settings.horizontal = true;
CefRefPtr<CefBoxLayout> top_panel_layout =
top_panel->SetToBoxLayout(top_panel_layout_settings);
// Add the buttons and URL textfield to |top_panel|.
for (size_t i = 0U; i < browse_buttons.size(); ++i)
top_panel->AddChildView(browse_buttons[i]);
top_panel->AddChildView(location_bar_);
UpdateExtensionControls();
DCHECK(extensions_panel_);
top_panel->AddChildView(extensions_panel_);
top_panel->AddChildView(menu_button_);
views_style::ApplyTo(top_panel);
// Allow |location| to grow and fill any remaining space.
top_panel_layout->SetFlexForView(location_bar_, 1);
top_toolbar_ = top_panel;
}
// Add the top panel and browser view to |window|.
int top_index = 0;
if (top_menu_panel)
window_->AddChildViewAt(top_menu_panel, top_index++);
window_->AddChildViewAt(top_toolbar_, top_index);
// Lay out |window| so we can get the default button sizes.
window_->Layout();
int min_width = 200;
if (!browse_buttons.empty()) {
// Make all browse buttons the same size.
MakeButtonsSameSize(browse_buttons);
// Lay out |window| again with the new button sizes.
window_->Layout();
// Minimum window width is the size of all buttons plus some extra.
min_width = browse_buttons[0]->GetBounds().width * 4 +
menu_button_->GetBounds().width + 100;
}
// Minimum window height is the hight of the top toolbar plus some extra.
int min_height = top_toolbar_->GetBounds().height + 100;
if (top_menu_panel)
min_height += top_menu_panel->GetBounds().height;
minimum_window_size_ = CefSize(min_width, min_height);
}
void ViewsWindow::AddAccelerators() {
// Trigger accelerators without first forwarding to web content.
browser_view_->SetPreferAccelerators(true);
@@ -926,7 +999,10 @@ void ViewsWindow::SetMenuFocusable(bool focusable) {
void ViewsWindow::EnableView(int id, bool enable) {
if (!window_)
return;
CefRefPtr<CefView> view = window_->GetViewForID(id);
// Special handling for |location_bar_| which may be an overlay (e.g. not a
// child of this view).
CefRefPtr<CefView> view =
id == ID_URL_TEXTFIELD ? location_bar_ : window_->GetViewForID(id);
if (view)
view->SetEnabled(enable);
}