cef/libcef/browser/views/view_view.h
Marshall Greenblatt f60476b848 views: Add support for OS and Chrome themes (fixes #3610, fixes #3671)
Controls now respect OS and Chrome themes by default for both Alloy
and Chrome runtimes. Chrome themes (mode and colors) can be configured
using the new CefRequestContext::SetChromeColorScheme method. Individual
theme colors can be overridden using the new CefWindowDelegate::
OnThemeColorsChanged and CefWindow::SetThemeColor methods.

The `--force-light-mode` and `--force-dark-mode` command-line flags are
now respected on all platforms as an override for the OS theme.

The current Chrome theme, if any, will take precedence over the OS theme
when determining light/dark status. On Windows and MacOS the titlebar
color will also be updated to match the light/dark theme.

Testable as follows:
- Run: `cefclient --enable-chrome-runtime` OR
       `cefclient --use-views --persist-user-preferences --cache-path=...`
  - App launches with default OS light/dark theme colors.
  - Change OS dark/light theme under system settings. Notice that theme
    colors change as expected.
  - Right click, select items from the new Theme sub-menu. Notice that
    theme colors behave as expected.
  - Exit and relaunch the app. Notice that the last-used theme colors are
    applied on app restart.
- Add `--background-color=green` to above command-line.
  - Perform the same actions as above. Notice that all controls start
    and remain green throughout (except some icons with Chrome runtime).
- Add `--force-light-mode` or `--force-dark-mode` to above command-line.
  - Perform the same actions as above. Notice that OS dark/light theme
    changes are ignored, but Chrome theme changes work as expected.
2024-04-09 16:19:35 -04:00

280 lines
9.5 KiB
C++

// Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
#ifndef CEF_LIBCEF_BROWSER_VIEWS_VIEW_VIEW_H_
#define CEF_LIBCEF_BROWSER_VIEWS_VIEW_VIEW_H_
#pragma once
#include "include/views/cef_view.h"
#include "include/views/cef_view_delegate.h"
#include "libcef/browser/thread_util.h"
#include "libcef/browser/views/view_util.h"
#include "base/logging.h"
#include "ui/views/accessibility/accessibility_paint_checks.h"
#include "ui/views/background.h"
#include "ui/views/view.h"
// Helpers for template boiler-plate.
#define CEF_VIEW_VIEW_T \
template <class ViewsViewClass, class CefViewDelegateClass>
#define CEF_VIEW_VIEW_A ViewsViewClass, CefViewDelegateClass
#define CEF_VIEW_VIEW_D CefViewView<CEF_VIEW_VIEW_A>
// Base template for implementing views::View-derived classes. The views::View-
// derived type passed to this template must provide a no-argument constructor
// (for example, see LabelButtonEx from basic_label_button_view.h). See comments
// in view_impl.h for a usage overview.
CEF_VIEW_VIEW_T class CefViewView : public ViewsViewClass {
public:
using ParentClass = ViewsViewClass;
// Should be created from CreateRootView() in a CefViewImpl-derived class.
// Do not call complex views::View-derived methods from a CefViewView-derived
// constructor as they may attempt to call back into CefViewImpl before
// registration has been performed. |cef_delegate| may be nullptr.
template <typename... Args>
explicit CefViewView(CefViewDelegateClass* cef_delegate, Args... args)
: ParentClass(args...), cef_delegate_(cef_delegate) {}
// Should be called from InitializeRootView() in the CefViewImpl-derived
// class that created this object. This method will be called after
// CefViewImpl registration has completed so it is safe to call complex
// views::View-derived methods here.
virtual void Initialize() {
// TODO(crbug.com/1218186): Remove this, if this view is focusable then it
// needs to add a name so that the screen reader knows what to announce.
ParentClass::SetProperty(views::kSkipAccessibilityPaintChecks, true);
}
// Returns the CefViewDelegate-derived delegate associated with this view.
// May return nullptr.
CefViewDelegateClass* cef_delegate() const { return cef_delegate_; }
// Returns the CefView associated with this view. May return nullptr during
// CefViewImpl initialization. If callbacks to the CefViewImpl-derived class
// are required define an interface that the CefViewImpl-derived class can
// implement and pass as an unowned instance to this object's constructor (see
// for example CefWindowView).
CefRefPtr<CefView> GetCefView() const {
CefRefPtr<CefView> view = view_util::GetFor(this, false);
DCHECK(view);
return view;
}
// views::View methods:
gfx::Size CalculatePreferredSize() const override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
int GetHeightForWidth(int w) const override;
void Layout(views::View::PassKey) override;
void ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) override;
void AddedToWidget() override;
void RemovedFromWidget() override;
void OnFocus() override;
void OnBlur() override;
void OnThemeChanged() override;
// Return true if this View is expected to have a minimum size (for example,
// a button where the minimum size is based on the label).
virtual bool HasMinimumSize() const { return false; }
private:
void NotifyChildViewChanged(
const views::ViewHierarchyChangedDetails& details);
void NotifyParentViewChanged(
const views::ViewHierarchyChangedDetails& details);
// Not owned by this object.
CefViewDelegateClass* const cef_delegate_;
};
CEF_VIEW_VIEW_T gfx::Size CEF_VIEW_VIEW_D::CalculatePreferredSize() const {
gfx::Size result;
if (cef_delegate()) {
CefSize cef_size = cef_delegate()->GetPreferredSize(GetCefView());
if (!cef_size.IsEmpty()) {
result = gfx::Size(cef_size.width, cef_size.height);
}
}
if (result.IsEmpty()) {
result = ParentClass::CalculatePreferredSize();
}
if (result.IsEmpty()) {
// Some layouts like BoxLayout expect the preferred size to be non-empty.
// The user may have set the size explicitly. Therefore return the current
// size as the preferred size.
result = ParentClass::size();
}
return result;
}
CEF_VIEW_VIEW_T gfx::Size CEF_VIEW_VIEW_D::GetMinimumSize() const {
gfx::Size result;
if (cef_delegate()) {
CefSize cef_size = cef_delegate()->GetMinimumSize(GetCefView());
if (!cef_size.IsEmpty()) {
result = gfx::Size(cef_size.width, cef_size.height);
}
}
// We don't want to call ParentClass::GetMinimumSize() in all cases because
// the default views::View implementation will call GetPreferredSize(). That
// may result in size() being returned which keeps the View from shrinking.
if (result.IsEmpty() && HasMinimumSize()) {
result = ParentClass::GetMinimumSize();
}
return result;
}
CEF_VIEW_VIEW_T gfx::Size CEF_VIEW_VIEW_D::GetMaximumSize() const {
gfx::Size result;
if (cef_delegate()) {
CefSize cef_size = cef_delegate()->GetMaximumSize(GetCefView());
if (!cef_size.IsEmpty()) {
result = gfx::Size(cef_size.width, cef_size.height);
}
}
if (result.IsEmpty()) {
result = ParentClass::GetMaximumSize();
}
return result;
}
CEF_VIEW_VIEW_T int CEF_VIEW_VIEW_D::GetHeightForWidth(int w) const {
int result = 0;
if (cef_delegate()) {
result = cef_delegate()->GetHeightForWidth(GetCefView(), w);
}
if (result == 0) {
result = ParentClass::GetHeightForWidth(w);
}
if (result == 0) {
// Some layouts like FillLayout will ignore the preferred size if this view
// has no children. We want to use the preferred size if not otherwise
// specified.
result = ParentClass::GetPreferredSize().height();
}
return result;
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::Layout(views::View::PassKey) {
ParentClass::template LayoutSuperclass<ParentClass>(this);
// If Layout() did not provide a size then use the preferred size.
if (ParentClass::size().IsEmpty()) {
ParentClass::SizeToPreferredSize();
}
if (cef_delegate()) {
const auto new_bounds = ParentClass::bounds();
CefRect new_rect(new_bounds.x(), new_bounds.y(), new_bounds.width(),
new_bounds.height());
cef_delegate()->OnLayoutChanged(GetCefView(), new_rect);
}
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
NotifyChildViewChanged(details);
NotifyParentViewChanged(details);
ParentClass::ViewHierarchyChanged(details);
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::AddedToWidget() {
ParentClass::AddedToWidget();
if (cef_delegate()) {
cef_delegate()->OnWindowChanged(GetCefView(), /*added=*/true);
}
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::RemovedFromWidget() {
if (cef_delegate()) {
cef_delegate()->OnWindowChanged(GetCefView(), /*added=*/false);
}
ParentClass::RemovedFromWidget();
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::OnFocus() {
if (cef_delegate()) {
cef_delegate()->OnFocus(GetCefView());
}
ParentClass::OnFocus();
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::OnBlur() {
if (cef_delegate()) {
cef_delegate()->OnBlur(GetCefView());
}
ParentClass::OnBlur();
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::OnThemeChanged() {
// Clear the background, if set.
if (ParentClass::background()) {
ParentClass::SetBackground(nullptr);
}
// Apply default theme colors.
ParentClass::OnThemeChanged();
// Allow the client to override the default colors.
if (cef_delegate()) {
cef_delegate()->OnThemeChanged(GetCefView());
}
// If the background is still unset then possibly set it to the desired value.
if (!ParentClass::background()) {
// May return an empty value.
const auto& color =
view_util::GetBackgroundColor(this, /*allow_transparent=*/true);
if (color) {
ParentClass::SetBackground(views::CreateSolidBackground(*color));
}
}
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::NotifyChildViewChanged(
const views::ViewHierarchyChangedDetails& details) {
if (!cef_delegate()) {
return;
}
// Only interested with the parent is |this| object and the notification is
// about an immediate child (notifications are also sent for grandchildren).
if (details.parent != this || details.child->parent() != this) {
return;
}
// Only notify for children that have a known CEF root view. For example,
// don't notify when ScrollView adds child scroll bars.
CefRefPtr<CefView> child = view_util::GetFor(details.child, false);
if (child) {
cef_delegate()->OnChildViewChanged(GetCefView(), details.is_add, child);
}
}
CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::NotifyParentViewChanged(
const views::ViewHierarchyChangedDetails& details) {
if (!cef_delegate()) {
return;
}
// Only interested when the child is |this| object and notification is about
// the immediate parent (notifications are sent for all parents).
if (details.child != this || details.parent != ParentClass::parent()) {
return;
}
// The immediate parent might be an intermediate view so find the closest
// known CEF root view. |parent| might be nullptr for overlays.
CefRefPtr<CefView> parent = view_util::GetFor(details.parent, true);
if (parent) {
cef_delegate()->OnParentViewChanged(GetCefView(), details.is_add, parent);
}
}
#endif // CEF_LIBCEF_BROWSER_VIEWS_VIEW_VIEW_H_