| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef UI_NATIVE_THEME_NATIVE_THEME_H_ |
| #define UI_NATIVE_THEME_NATIVE_THEME_H_ |
| |
| #include <stddef.h> |
| |
| #include <optional> |
| #include <variant> |
| |
| #include "base/callback_list.h" |
| #include "base/component_export.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/observer_list.h" |
| #include "base/sequence_checker.h" |
| #include "base/time/time.h" |
| #include "base/types/pass_key.h" |
| #include "build/build_config.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/models/menu_separator_types.h" |
| #include "ui/color/color_id.h" |
| #include "ui/color/color_provider_key.h" |
| #include "ui/color/system_theme.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace cc { |
| class PaintCanvas; |
| } |
| |
| namespace ui { |
| |
| class ColorProvider; |
| class NativeThemeObserver; |
| |
| // This class supports drawing UI controls (like buttons, text fields, lists, |
| // comboboxes, etc) that look like the native UI controls of the underlying |
| // platform, such as Windows or Linux. It also supplies default colors for |
| // dialog box backgrounds, etc., which are obtained from the system theme where |
| // possible. |
| // |
| // The supported control types are listed in the Part enum. These parts can be |
| // in any state given by the State enum, where the actual definition of the |
| // state is part-specific. The supported colors are listed in the ColorId enum. |
| // |
| // Some parts require more information than simply the state in order to be |
| // drawn correctly, and this information is given to the Paint() method via the |
| // ExtraParams union. Each part that requires more information has its own |
| // field in the union. |
| // |
| // NativeTheme also supports getting the default size of a given part with |
| // the GetPartSize() method. |
| class COMPONENT_EXPORT(NATIVE_THEME) NativeTheme { |
| public: |
| // A part being sized or painted. |
| enum Part { |
| kCheckbox, |
| #if BUILDFLAG(IS_LINUX) |
| kFrameTopArea, |
| #endif |
| kInnerSpinButton, |
| kMenuList, |
| kMenuPopupBackground, |
| #if BUILDFLAG(IS_WIN) |
| kMenuCheck, |
| kMenuCheckBackground, |
| kMenuPopupArrow, |
| kMenuPopupGutter, |
| #endif |
| kMenuPopupSeparator, |
| kMenuItemBackground, |
| kProgressBar, |
| kPushButton, |
| kRadio, |
| |
| // The order of these enums is important, do not change without also |
| // changing the code in platform implementations. |
| kScrollbarDownArrow, |
| kScrollbarLeftArrow, |
| kScrollbarRightArrow, |
| kScrollbarUpArrow, |
| |
| kScrollbarHorizontalThumb, |
| kScrollbarVerticalThumb, |
| kScrollbarHorizontalTrack, |
| kScrollbarVerticalTrack, |
| kScrollbarHorizontalGripper, |
| kScrollbarVerticalGripper, |
| // The corner is drawn when there is both a horizontal and vertical |
| // scrollbar. |
| kScrollbarCorner, |
| |
| kSliderTrack, |
| kSliderThumb, |
| kTabPanelBackground, |
| kTextField, |
| kTrackbarThumb, |
| kTrackbarTrack, |
| kWindowResizeGripper, |
| kMaxPart, |
| }; |
| |
| // The state of some part being sized or painted. |
| enum State { |
| // CAUTION: These values are used as array indexes. |
| kDisabled = 0, |
| kHovered = 1, |
| kNormal = 2, |
| kPressed = 3, |
| kNumStates = kPressed + 1, |
| }; |
| |
| enum class PreferredColorScheme { |
| kNoPreference = 0, |
| kLight = 1, |
| kDark = 2, |
| kMaxValue = kDark, |
| }; |
| |
| enum class PreferredContrast { |
| kNoPreference = 0, |
| kMore = 1, |
| kLess = 2, |
| kCustom = 3, // E.g. forced colors outside of a contrast-related setting. |
| kMaxValue = kCustom, |
| }; |
| |
| // Each structure below holds extra information needed when painting a given |
| // part. |
| |
| struct ButtonExtraParams { |
| bool checked = false; |
| bool indeterminate = false; // Whether the button state is indeterminate. |
| bool is_default = false; // Whether the button is default button. |
| bool is_focused = false; |
| bool has_border = false; |
| int classic_state = 0; // Used on Windows when uxtheme is not available. |
| SkColor background_color = gfx::kPlaceholderColor; |
| float zoom = 0; |
| }; |
| |
| struct FrameTopAreaExtraParams { |
| // Distinguishes between active (foreground) and inactive |
| // (background) window frame styles. |
| bool is_active = false; |
| // True when Chromium renders the titlebar. False when the window |
| // manager renders the titlebar. |
| bool use_custom_frame = false; |
| // If the NativeTheme will paint a solid color, it should use |
| // |default_background_color|. |
| SkColor default_background_color = gfx::kPlaceholderColor; |
| }; |
| |
| enum class SpinArrowsDirection : int { |
| kLeftRight, |
| kUpDown, |
| }; |
| |
| struct InnerSpinButtonExtraParams { |
| bool spin_up = false; |
| bool read_only = false; |
| SpinArrowsDirection spin_arrows_direction = SpinArrowsDirection::kUpDown; |
| int classic_state = 0; // Used on Windows when uxtheme is not available. |
| }; |
| |
| struct MenuArrowExtraParams { |
| bool pointing_right = false; |
| // Used for the disabled state to indicate if the item is both disabled and |
| // selected. |
| bool is_selected = false; |
| }; |
| |
| struct MenuCheckExtraParams { |
| bool is_radio = false; |
| // Used for the disabled state to indicate if the item is both disabled and |
| // selected. |
| bool is_selected = false; |
| }; |
| |
| struct MenuSeparatorExtraParams { |
| raw_ptr<const gfx::Rect> paint_rect = nullptr; |
| ColorId color_id = kColorMenuSeparator; |
| MenuSeparatorType type = MenuSeparatorType::NORMAL_SEPARATOR; |
| }; |
| |
| struct MenuItemExtraParams { |
| bool is_selected = false; |
| int corner_radius = 0; |
| }; |
| |
| enum class ArrowDirection : int { |
| kDown, |
| kLeft, |
| kRight, |
| }; |
| |
| struct COMPONENT_EXPORT(NATIVE_THEME) MenuListExtraParams { |
| bool has_border = false; |
| bool has_border_radius = false; |
| int arrow_x = 0; |
| int arrow_y = 0; |
| int arrow_size = 0; |
| ArrowDirection arrow_direction = ArrowDirection::kDown; |
| SkColor arrow_color = gfx::kPlaceholderColor; |
| SkColor background_color = gfx::kPlaceholderColor; |
| int classic_state = 0; // Used on Windows when uxtheme is not available. |
| float zoom = 0; |
| |
| MenuListExtraParams(); |
| MenuListExtraParams(const MenuListExtraParams&); |
| MenuListExtraParams& operator=(const MenuListExtraParams&); |
| }; |
| |
| struct MenuBackgroundExtraParams { |
| int corner_radius = 0; |
| }; |
| |
| struct ProgressBarExtraParams { |
| double animated_seconds = 0; |
| bool determinate = false; |
| int value_rect_x = 0; |
| int value_rect_y = 0; |
| int value_rect_width = 0; |
| int value_rect_height = 0; |
| float zoom = 0; |
| bool is_horizontal = false; |
| }; |
| |
| struct ScrollbarArrowExtraParams { |
| bool is_hovering = false; |
| float zoom = 0; |
| bool needs_rounded_corner = false; |
| bool right_to_left = false; |
| // These allow clients to directly override the color values to support |
| // element-specific web platform CSS. |
| std::optional<SkColor> thumb_color; |
| std::optional<SkColor> track_color; |
| }; |
| |
| struct ScrollbarTrackExtraParams { |
| bool is_upper = false; |
| int track_x = 0; |
| int track_y = 0; |
| int track_width = 0; |
| int track_height = 0; |
| int classic_state = 0; // Used on Windows when uxtheme is not available. |
| // This allows clients to directly override the color values to support |
| // element-specific web platform CSS. |
| std::optional<SkColor> track_color; |
| }; |
| |
| struct ScrollbarThumbExtraParams { |
| bool is_hovering = false; |
| // This allows clients to directly override the color values to support |
| // element-specific web platform CSS. |
| std::optional<SkColor> thumb_color; |
| std::optional<SkColor> track_color; |
| bool is_thumb_minimal_mode = false; |
| bool is_web_test = false; |
| }; |
| |
| #if BUILDFLAG(IS_APPLE) |
| enum ScrollbarOrientation { |
| // Vertical scrollbar on the right side of content. |
| kVerticalOnRight, |
| // Vertical scrollbar on the left side of content. |
| kVerticalOnLeft, |
| // Horizontal scrollbar (on the bottom of content). |
| kHorizontal, |
| }; |
| |
| // A unique set of scrollbar params. Currently needed for Mac. |
| struct ScrollbarExtraParams { |
| bool is_hovering = false; |
| bool is_overlay = false; |
| ScrollbarOrientation orientation = |
| ScrollbarOrientation::kVerticalOnRight; // Used on Mac for drawing |
| // gradients. |
| float scale_from_dip = 0; |
| // These allow clients to directly override the color values to support |
| // element-specific web platform CSS. |
| std::optional<SkColor> thumb_color; |
| std::optional<SkColor> track_color; |
| }; |
| #endif |
| |
| struct SliderExtraParams { |
| bool vertical = false; |
| bool in_drag = false; |
| int thumb_x = 0; |
| int thumb_y = 0; |
| float zoom = 0; |
| bool right_to_left = false; |
| }; |
| |
| struct COMPONENT_EXPORT(NATIVE_THEME) TextFieldExtraParams { |
| bool is_text_area = false; |
| bool is_listbox = false; |
| SkColor background_color = gfx::kPlaceholderColor; |
| bool is_read_only = false; |
| bool is_focused = false; |
| bool fill_content_area = false; |
| bool draw_edges = false; |
| int classic_state = 0; // Used on Windows when uxtheme is not available. |
| bool has_border = false; |
| bool auto_complete_active = false; |
| float zoom = 0; |
| |
| TextFieldExtraParams(); |
| TextFieldExtraParams(const TextFieldExtraParams&); |
| TextFieldExtraParams& operator=(const TextFieldExtraParams&); |
| }; |
| |
| struct TrackbarExtraParams { |
| bool vertical = false; |
| int classic_state = 0; // Used on Windows when uxtheme is not available. |
| }; |
| |
| using ExtraParams = std::variant<ButtonExtraParams, |
| FrameTopAreaExtraParams, |
| InnerSpinButtonExtraParams, |
| MenuArrowExtraParams, |
| MenuCheckExtraParams, |
| MenuItemExtraParams, |
| MenuSeparatorExtraParams, |
| MenuListExtraParams, |
| MenuBackgroundExtraParams, |
| ProgressBarExtraParams, |
| ScrollbarArrowExtraParams, |
| #if BUILDFLAG(IS_APPLE) |
| ScrollbarExtraParams, |
| #endif |
| ScrollbarTrackExtraParams, |
| ScrollbarThumbExtraParams, |
| SliderExtraParams, |
| TextFieldExtraParams, |
| TrackbarExtraParams>; |
| |
| // Creating an instance of this class prevents `NotifyOnNativeThemeUpdated()` |
| // from having an effect in any `NativeTheme` instance until no scopers |
| // remain. When the last scoper is destroyed, any such delayed notifications |
| // will be fired. |
| class [[maybe_unused, nodiscard]] COMPONENT_EXPORT(NATIVE_THEME) |
| UpdateNotificationDelayScoper { |
| public: |
| UpdateNotificationDelayScoper(); |
| UpdateNotificationDelayScoper(const UpdateNotificationDelayScoper&); |
| UpdateNotificationDelayScoper(UpdateNotificationDelayScoper&&); |
| UpdateNotificationDelayScoper& operator=( |
| const UpdateNotificationDelayScoper&) = default; |
| UpdateNotificationDelayScoper& operator=(UpdateNotificationDelayScoper&&) = |
| default; |
| ~UpdateNotificationDelayScoper(); |
| |
| static bool exists(base::PassKey<NativeTheme>) { return !!num_instances_; } |
| |
| static base::CallbackListSubscription RegisterCallback( |
| base::PassKey<NativeTheme>, |
| base::OnceClosure cb); |
| |
| private: |
| static base::OnceClosureList& GetDelayedNotifications(); |
| |
| static size_t num_instances_; |
| }; |
| |
| NativeTheme(const NativeTheme&) = delete; |
| NativeTheme& operator=(const NativeTheme&) = delete; |
| |
| // Returns shared instances of the default native theme for native UI or the |
| // web, respectively. |
| static NativeTheme* GetInstanceForNativeUi(); |
| static NativeTheme* GetInstanceForWeb(); |
| |
| // Convenience methods to scale a width/radius by a zoom factor. |
| static float AdjustBorderWidthByZoom(float border_width, float zoom_level); |
| static float AdjustBorderRadiusByZoom(Part part, |
| float border_radius, |
| float zoom_level); |
| |
| virtual gfx::Size GetPartSize(Part part, |
| State state, |
| const ExtraParams& extra_params) const; |
| |
| virtual int GetPaintedScrollbarTrackInset() const; |
| |
| virtual gfx::Insets GetScrollbarSolidColorThumbInsets(Part part) const; |
| |
| virtual float GetBorderRadiusForPart(Part part, |
| float width, |
| float height) const; |
| |
| // Returns whether the theme uses a nine-patch resource for the given part. |
| // If true, calling code should always paint into a canvas the size of which |
| // can be gotten from GetNinePatchCanvasSize. |
| virtual bool SupportsNinePatch(Part part) const; |
| |
| // If the part paints into a nine-patch resource, the size of the canvas |
| // which should be painted into. |
| virtual gfx::Size GetNinePatchCanvasSize(Part part) const; |
| |
| // If the part paints into a nine-patch resource, the rect in the canvas |
| // which defines the center tile. This is the tile that should be resized out |
| // when the part is resized. |
| virtual gfx::Rect GetNinePatchAperture(Part part) const; |
| |
| // The scrollbar thumb color, if the theme uses a solid color for the |
| // scrollbar thumb. |
| virtual SkColor GetScrollbarThumbColor( |
| const ColorProvider* color_provider, |
| State state, |
| const ScrollbarThumbExtraParams& extra_params) const; |
| |
| // Returns the color the toolkit would use for a pressed button that has an |
| // unpressed color of `base_color`. |
| virtual SkColor GetSystemButtonPressedColor(SkColor base_color) const; |
| |
| // Registers this instance as an observer of `OsSettingsProvider` changes. |
| // This should not be called on an instance marked as the "associated web |
| // instance" of another theme, since in that case the other theme should |
| // notify about setting changes as necessary. |
| void BeginObservingOsSettingChanges(); |
| |
| // Adds or removes observers to be notified when the native theme changes. |
| void AddObserver(NativeThemeObserver* observer); |
| void RemoveObserver(NativeThemeObserver* observer); |
| |
| // Notifies observers that something has changed and they should reload |
| // settings if needed. This also resets the color provider cache. |
| // CAUTION: This is expensive; minimize unnecessary calls. |
| void NotifyOnNativeThemeUpdated(); |
| |
| // TODO(pkasting): Consider combining this with |
| // `NotifyOnNativeThemeUpdated()`. This would make it easy to move the |
| // underpinnings to the `OsSettingsProvider`, as well as replace |
| // `NativeThemeObserver` with a `CallbackList`. |
| void NotifyOnCaptionStyleUpdated(); |
| |
| // Paints the provided `part`/`state`. This is largely a wrapper around |
| // `PaintImpl()`. |
| void Paint( |
| cc::PaintCanvas* canvas, |
| const ColorProvider* color_provider, |
| Part part, |
| State state, |
| const gfx::Rect& rect, |
| const ExtraParams& extra_params, |
| bool forced_colors = false, |
| PreferredColorScheme color_scheme = PreferredColorScheme::kNoPreference, |
| PreferredContrast contrast = PreferredContrast::kNoPreference, |
| std::optional<SkColor> accent_color = std::nullopt) const; |
| |
| // Returns the key corresponding to this native theme object. |
| // Use `use_custom_frame` == true when Chromium renders the titlebar. |
| // False when the window manager renders the titlebar (currently GTK only). |
| ColorProviderKey GetColorProviderKey( |
| scoped_refptr<ColorProviderKey::ThemeInitializerSupplier> custom_theme, |
| bool use_custom_frame = true) const; |
| |
| // Accessors. |
| // |
| // NOTE: Be very cautious about using the setters here. |
| // * Tests generally should not modify `NativeTheme` state; if the goal is |
| // to pretend the underlying system is in a particular state, use |
| // `MockOsSettingsProvider` instead. |
| // |
| // * The values set below may be overwritten automatically, e.g. when system |
| // settings change; so if the goal is to override system-native behavior, |
| // "fire and forget" usage is insufficient. |
| // |
| // * To avoid jank from repeated notifications, these do not automatically |
| // call `NotifyOnNativeThemeUpdated()`. Failing to call that manually |
| // after using them typically results in cryptic bugs. |
| // |
| // TODO(pkasting): To address the third point, consider using |
| // `UpdateNotificationDelayScoper` everywhere that currently calls these |
| // setters or writes directly to the underlying members. Then make the setters |
| // call `NotifyOnNativeThemeUpdated()` whenever the actual value changes and |
| // change all direct writes to use the setters. At that point forgetting to |
| // notify will be impossible, but we will only get one such notification. |
| |
| SystemTheme system_theme() const { return system_theme_; } |
| |
| bool use_overlay_scrollbar() const { return use_overlay_scrollbar_; } |
| void set_use_overlay_scrollbar(bool use_overlay_scrollbar) { |
| use_overlay_scrollbar_ = use_overlay_scrollbar; |
| } |
| |
| ColorProviderKey::ForcedColors forced_colors() const { |
| return forced_colors_; |
| } |
| void set_forced_colors(ColorProviderKey::ForcedColors forced_colors) { |
| forced_colors_ = forced_colors; |
| } |
| |
| PreferredColorScheme preferred_color_scheme() const { |
| return preferred_color_scheme_; |
| } |
| void set_preferred_color_scheme(PreferredColorScheme preferred_color_scheme) { |
| preferred_color_scheme_ = preferred_color_scheme; |
| } |
| |
| PreferredContrast preferred_contrast() const { return preferred_contrast_; } |
| void set_preferred_contrast(PreferredContrast preferred_contrast) { |
| preferred_contrast_ = preferred_contrast; |
| } |
| |
| bool prefers_reduced_transparency() const { |
| return prefers_reduced_transparency_; |
| } |
| |
| bool inverted_colors() const { return inverted_colors_; } |
| |
| std::optional<SkColor> user_color() const { return user_color_; } |
| void set_user_color(std::optional<SkColor> user_color) { |
| user_color_ = user_color; |
| } |
| |
| std::optional<ColorProviderKey::SchemeVariant> scheme_variant() const { |
| return scheme_variant_; |
| } |
| |
| base::TimeDelta caret_blink_interval() const { return caret_blink_interval_; } |
| void set_caret_blink_interval(base::TimeDelta caret_blink_interval) { |
| caret_blink_interval_ = caret_blink_interval; |
| } |
| |
| protected: |
| explicit NativeTheme(SystemTheme system_theme = SystemTheme::kDefault); |
| virtual ~NativeTheme(); |
| |
| // Whether dark mode is forced via command-line flag. |
| static bool IsForcedDarkMode(); |
| |
| // Whether high contrast is forced via command-line flag. |
| static bool IsForcedHighContrast(); |
| |
| // Paints the provided `part`/`state`. Subclasses must override this if they |
| // want visible output. |
| virtual void PaintImpl(cc::PaintCanvas* canvas, |
| const ColorProvider* color_provider, |
| Part part, |
| State state, |
| const gfx::Rect& rect, |
| const ExtraParams& extra_params, |
| bool forced_colors, |
| bool dark_mode, |
| PreferredContrast contrast, |
| std::optional<SkColor> accent_color) const {} |
| |
| // Common implementation used by several subclasses. |
| virtual void PaintMenuItemBackground( |
| cc::PaintCanvas* canvas, |
| const ColorProvider* color_provider, |
| State state, |
| const gfx::Rect& rect, |
| const MenuItemExtraParams& extra_params) const; |
| |
| // Called when toolkit settings change. Updates affected variables. If |
| // anything changes or `force_notify` is set, notifies observers. |
| virtual void OnToolkitSettingsChanged(bool force_notify); |
| |
| // Instructs this theme instance to mirror various appearance settings to |
| // `associated_web_instance` when they change. |
| void SetAssociatedWebInstance(NativeTheme* associated_web_instance); |
| |
| // Updates the settings of any `associated_web_instance_` to match this |
| // instance's current settings. Returns whether anything was changed. |
| bool UpdateWebInstance() const; |
| |
| private: |
| // Updates web instance and notifies observers something has changed. |
| void NotifyOnNativeThemeUpdatedImpl(); |
| |
| // Updates variables affected by toolkit settings and returns whether anything |
| // changed as a result. |
| bool UpdateVariablesForToolkitSettings(); |
| |
| // Calculates and returns appropriate values based on flags and toolkit. |
| ColorProviderKey::ForcedColors CalculateForcedColors() const; |
| PreferredColorScheme CalculatePreferredColorScheme() const; |
| PreferredContrast CalculatePreferredContrast() const; |
| |
| base::CallbackListSubscription os_settings_changed_subscription_; |
| base::CallbackListSubscription update_delay_subscription_; |
| base::ObserverList<NativeThemeObserver> native_theme_observers_; |
| SystemTheme system_theme_; |
| bool use_overlay_scrollbar_ = false; |
| ColorProviderKey::ForcedColors forced_colors_ = |
| ColorProviderKey::ForcedColors::kNone; |
| PreferredColorScheme preferred_color_scheme_ = |
| PreferredColorScheme::kNoPreference; |
| PreferredContrast preferred_contrast_ = PreferredContrast::kNoPreference; |
| bool prefers_reduced_transparency_ = false; |
| bool inverted_colors_ = false; |
| std::optional<SkColor> user_color_; |
| std::optional<ColorProviderKey::SchemeVariant> scheme_variant_; |
| ColorProviderKey::UserColorSource preferred_color_source_ = |
| ColorProviderKey::UserColorSource::kAccent; |
| base::TimeDelta caret_blink_interval_; |
| |
| raw_ptr<NativeTheme> associated_web_instance_ = nullptr; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| } // namespace ui |
| |
| #endif // UI_NATIVE_THEME_NATIVE_THEME_H_ |