blob: b6e3a2f890c1ee1278446b53b51b1b49e3ef7445 [file] [log] [blame] [view]
Wei Li098f52712020-07-10 20:26:121# Example: Theme-Aware UI
2
3[TOC]
4
5## Introduction
6
7A common pitfall in UI development is failing to handle varying themes and
8colors. A perfect looking UI may become illegible after a theme change or on a
9different background color, which can happen when changing the Chrome or OS
10theme.
11
12This example shows you sample UI for a common scenario: click an icon to show
13a dialog. A checkbox dynamically switches between light and dark themes to
14demonstrate how the UI responds to theme changes. In this example, you can
15learn a few common techniques to make UI handle theme changes correctly.
16
17 
18
19| |
20| :---: |
21| ![Light theme UI](theme_aware_light.png) |
22| Figure 1. Sample UI in light theme |
23
24 
25
26| |
27| :---: |
28| ![Dark theme UI](theme_aware_dark.png) |
29| Figure 2. Sample UI in dark theme |
30
31
32## Prerequisites
33
34This example assumes you are already familiar with Views toolkit fundamentals,
35such as how to lay out UI elements and how to customize them.
36
37
Wei Li4c62d3f2020-07-17 00:26:0538## Run the example
Wei Li098f52712020-07-10 20:26:1239
40The example code is in the file
John Palmer046f9872021-05-24 01:24:5641[`ui/views/examples/colored_dialog_example.cc`](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/examples/colored_dialog_example.cc)
Wei Li098f52712020-07-10 20:26:1242and its corresponding header file. You can run it on Windows or Linux via
43the `views_examples` application. Change the path accordingly based on your
44platform and building environment:
45
46
47``` shell
48$ autoninja -C out\Default views_examples
49$ out\Default\views_examples --enable-examples="Colored Dialog"
50```
51
52
Wei Li4c62d3f2020-07-17 00:26:0553## Technique 1: use existing Views controls
Wei Li098f52712020-07-10 20:26:1254
55The example's dialog consists of a title, a text field, and two buttons.
56For all these components, you can use Views’ existing controls. The existing
57controls were developed to be theme-aware and use correct colors by default.
58When used appropriately, they should require no extra effort to handle dynamic
59color changes.
60
61The following code snippet shows the creation of all the UI elements in the
62dialog. Note how no color-specific code or dynamic theme changing handling
63is necessary.
64
65
66``` cpp
67 SetTitle(l10n_util::GetStringUTF16(IDS_COLORED_DIALOG_TITLE));
68
69 SetLayoutManager(std::make_unique<views::FillLayout>());
70 set_margins(views::LayoutProvider::Get()->GetDialogInsetsForContentType(
Hwanseung Lee6d2d6a82021-04-15 02:48:5171 views::DialogContentType::kControl, views::DialogContentType::kControl));
Wei Li098f52712020-07-10 20:26:1272
73 textfield_ = AddChildView(std::make_unique<views::Textfield>());
74 textfield_->SetPlaceholderText(
75 l10n_util::GetStringUTF16(IDS_COLORED_DIALOG_TEXTFIELD_PLACEHOLDER));
76 textfield_->SetAccessibleName(
77 l10n_util::GetStringUTF16(IDS_COLORED_DIALOG_TEXTFIELD_AX_LABEL));
78 textfield_->set_controller(this);
79
80 SetButtonLabel(ui::DIALOG_BUTTON_OK,
81 l10n_util::GetStringUTF16(IDS_COLORED_DIALOG_SUBMIT_BUTTON));
82 SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
83```
84
85
Wei Li4c62d3f2020-07-17 00:26:0586## Technique 2: override `OnThemeChanged()` in custom controls
Wei Li098f52712020-07-10 20:26:1287
88The checkbox in the main UI overrides `OnThemeChanged()` to implement
89customized behavior (in this case, automatically adjusting its visible state
90to externally-triggered theme changes). This method is called every time the
91theme changes, including when a `View` is first shown.
92
93
94``` cpp
Peter Kasting7e3f3542020-11-04 20:37:2995class ThemeTrackingCheckbox : public views::Checkbox {
Wei Li098f52712020-07-10 20:26:1296 public:
Jan Wilken Dörrie85285b02021-03-11 23:38:4797 explicit ThemeTrackingCheckbox(const std::u16string& label)
Wei Li098f52712020-07-10 20:26:1298 : Checkbox(label, this) {}
99 ThemeTrackingCheckbox(const ThemeTrackingCheckbox&) = delete;
100 ThemeTrackingCheckbox& operator=(const ThemeTrackingCheckbox&) = delete;
101 ~ThemeTrackingCheckbox() override = default;
102
103 // views::Checkbox:
104 void OnThemeChanged() override {
105 views::Checkbox::OnThemeChanged();
106
107 // Without this, the checkbox would not update for external (e.g. OS-driven)
108 // theme changes.
109 SetChecked(GetNativeTheme()->ShouldUseDarkColors());
110 }
111
Peter Kasting7e3f3542020-11-04 20:37:29112 void ButtonPressed() {
Wei Li098f52712020-07-10 20:26:12113 GetNativeTheme()->set_use_dark_colors(GetChecked());
114
115 // An OS or Chrome theme change would do this automatically.
116 GetWidget()->ThemeChanged();
117 }
118};
119```
120
121
122When creating controls using custom colors, setting the colors in
123`OnThemeChanged()` (instead of in the constructor or in a call from another
124object) ensures they will always be read from an up-to-date source and reset
125whenever the theme changes. By contrast, setting them in the constructor will
126not handle theme changes while the control is visible, and (depending on how
127the colors are calculated) may not even work correctly to begin with.
128
129
Wei Li4c62d3f2020-07-17 00:26:05130## Technique 3: use theme neutral icons and images
Wei Li098f52712020-07-10 20:26:12131
132The button in the main UI contains an icon. Using a vector icon (as shown in
133the example) makes it easy to re-rasterize to the correct color any time the
134theme changes.
135
136
137``` cpp
138AddChildView(std::make_unique<TextVectorImageButton>(
Peter Kasting7e3f3542020-11-04 20:37:29139 base::BindRepeating(&ColoredDialogChooser::ButtonPressed,
140 base::Unretained(this)),
141 l10n_util::GetStringUTF16(IDS_COLORED_DIALOG_CHOOSER_BUTTON),
Wei Li098f52712020-07-10 20:26:12142 views::kInfoIcon));
143```
144
145While it's possible to create theme-aware or theme-neutral UI with bitmap
146images as well, it's generally more difficult. Since vector icons typically
147also provide better support for different scale factors than bitmaps do,
148vector imagery is preferable in most cases.
149
150The following code snippet shows how to make the icon color adapt to the theme
151change.
152
153``` cpp
154class TextVectorImageButton : public views::MdTextButton {
155public:
Peter Kasting7e3f3542020-11-04 20:37:29156 TextVectorImageButton(PressedCallback callback,
Jan Wilken Dörrie85285b02021-03-11 23:38:47157 const std::u16string& text,
Wei Li098f52712020-07-10 20:26:12158 const gfx::VectorIcon& icon)
Peter Kasting7e3f3542020-11-04 20:37:29159 : MdTextButton(std::move(callback), text), icon_(icon) {}
Wei Li098f52712020-07-10 20:26:12160 TextVectorImageButton(const TextVectorImageButton&) = delete;
161 TextVectorImageButton& operator=(const TextVectorImageButton&) = delete;
162 ~TextVectorImageButton() override = default;
163
164 void OnThemeChanged() override {
165 views::MdTextButton::OnThemeChanged();
166
167 // Use the text color for the associated vector image.
168 SetImage(views::Button::ButtonState::STATE_NORMAL,
169 gfx::CreateVectorIcon(icon_, label()->GetEnabledColor()));
170 }
171```
172
173
Wei Li4c62d3f2020-07-17 00:26:05174## Learn more
Wei Li098f52712020-07-10 20:26:12175
176To experiment with all Views examples and controls, run the examples app
177without any argument, which will show a list of all the examples and controls
178you can try out:
179
180
181``` shell
182$ out\Default\views_examples
183```
184
185For more in-depth recommendations on working with colors in Views, read
186[Best Practice: Colors](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ui/learn/bestpractices/colors.md).
187