| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/capture_mode/key_combo_view.h" |
| |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "ash/accelerators/keyboard_code_util.h" |
| #include "ash/capture_mode/key_item_view.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/keyboard_codes_posix.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/view.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr auto kBetweenKeyItemSpace = 8; |
| |
| // Returns the vector icons for keys that have icons on the keyboard. |
| const gfx::VectorIcon* GetVectorIconForDemoTools(ui::KeyboardCode key_code) { |
| switch (key_code) { |
| case ui::VKEY_COMMAND: |
| return GetSearchOrLauncherVectorIcon(); |
| case ui::VKEY_ASSISTANT: |
| return &kCaptureModeDemoToolsAssistantIcon; |
| case ui::VKEY_SETTINGS: |
| return &kCaptureModeDemoToolsMenuIcon; |
| default: |
| return GetVectorIconForKeyboardCode(key_code); |
| } |
| } |
| |
| std::unique_ptr<KeyItemView> CreateKeyItemView(ui::KeyboardCode key_code) { |
| std::unique_ptr key_item_view = std::make_unique<KeyItemView>(key_code); |
| const gfx::VectorIcon* vector_icon = GetVectorIconForDemoTools(key_code); |
| if (vector_icon) { |
| key_item_view->SetIcon(*vector_icon); |
| } else { |
| std::u16string key_item_string = |
| GetStringForKeyboardCode(key_code, /*remap_positional_key=*/false); |
| key_item_view->SetText(key_item_string); |
| } |
| return key_item_view; |
| } |
| |
| ui::KeyboardCode DecodeModifier(int modifier) { |
| switch (modifier) { |
| case ui::EF_CONTROL_DOWN: |
| return ui::VKEY_CONTROL; |
| case ui::EF_ALT_DOWN: |
| return ui::VKEY_MENU; |
| case ui::EF_SHIFT_DOWN: |
| return ui::VKEY_SHIFT; |
| case ui::EF_COMMAND_DOWN: |
| return ui::VKEY_COMMAND; |
| default: |
| return ui::VKEY_UNKNOWN; |
| } |
| } |
| |
| bool operator>(const ui::KeyboardCode lhs, const ui::KeyboardCode rhs) { |
| auto digitize_modifier_key_code = [&](ui::KeyboardCode key_code) { |
| switch (key_code) { |
| case ui::VKEY_CONTROL: |
| return 0; |
| case ui::VKEY_MENU: |
| return 1; |
| case ui::VKEY_SHIFT: |
| return 2; |
| case ui::VKEY_COMMAND: |
| return 3; |
| default: |
| return 1000; |
| } |
| }; |
| |
| return digitize_modifier_key_code(lhs) > digitize_modifier_key_code(rhs); |
| } |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // ModifiersContainerView: |
| |
| // The container view that hosts the modifiers key item views. |
| class ModifiersContainerView : public views::View { |
| METADATA_HEADER(ModifiersContainerView, views::View) |
| |
| public: |
| explicit ModifiersContainerView() { |
| views::BoxLayout* layout_manager = |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, gfx::Insets::VH(0, 0), |
| kBetweenKeyItemSpace)); |
| layout_manager->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| } |
| |
| ModifiersContainerView(const ModifiersContainerView&) = delete; |
| ModifiersContainerView& operator=(const ModifiersContainerView&) = delete; |
| ~ModifiersContainerView() override = default; |
| |
| void RebuildModifiersContainerView(int new_modifiers) { |
| // Use XOR to filter out the modifiers that changed, i.e. the 1s in the |
| // `diff` bit fields that correspond to the modifiers that changed. |
| const int diff = current_modifiers_ ^ new_modifiers; |
| for (const auto modifier : {ui::EF_CONTROL_DOWN, ui::EF_ALT_DOWN, |
| ui::EF_SHIFT_DOWN, ui::EF_COMMAND_DOWN}) { |
| if ((modifier & diff) == 0) { |
| continue; |
| } |
| |
| const ui::KeyboardCode key_code = DecodeModifier(modifier); |
| |
| // Use AND to decide whether we want to do adding or removal operation. If |
| // this `modifier` is set in the `new_modifiers` that means it has been |
| // recently pressed, which means a new `KeyItemView` needs to be added. |
| // Otherwise, it means a key has been recently released, and we should |
| // remove its reoccoresponding `KeyItemView`. |
| if ((modifier & new_modifiers) != 0) { |
| AddModifier(key_code); |
| } else { |
| RemoveModifier(key_code); |
| } |
| } |
| |
| current_modifiers_ = new_modifiers; |
| } |
| |
| private: |
| void RemoveModifier(ui::KeyboardCode key_code) { |
| for (views::View* child : children()) { |
| if (static_cast<KeyItemView*>(child)->key_code() == key_code) { |
| RemoveChildViewT(child); |
| return; |
| } |
| } |
| } |
| |
| void AddModifier(ui::KeyboardCode key_code) { |
| // We're trying to find the view whose `key_code()` is greater than the |
| // given `key_code` so that we can insert a new `KeyItemView` at that index, |
| // or at the end if no such view was found. This keeps the modifiers sorted |
| // as `Ctrl < Alt < Shift < Search`. |
| const auto iter = |
| base::ranges::find_if(children(), [key_code](views::View* child) { |
| return static_cast<KeyItemView*>(child)->key_code() > key_code; |
| }); |
| |
| AddChildViewAt(CreateKeyItemView(key_code), |
| std::distance(children().begin(), iter)); |
| } |
| |
| int current_modifiers_ = 0; |
| }; |
| |
| BEGIN_METADATA(ModifiersContainerView) |
| END_METADATA |
| |
| // ----------------------------------------------------------------------------- |
| // KeyComboView: |
| |
| KeyComboView::KeyComboView() { |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), |
| kBetweenKeyItemSpace)); |
| } |
| |
| KeyComboView::~KeyComboView() = default; |
| |
| void KeyComboView::RefreshView(int modifiers, |
| ui::KeyboardCode last_non_modifier_key) { |
| if (modifiers_ != modifiers) { |
| modifiers_ = modifiers; |
| if (!modifiers_container_view_) { |
| modifiers_container_view_ = AddChildViewAt( |
| std::make_unique<ModifiersContainerView>(), /*index=*/0); |
| } |
| |
| modifiers_container_view_->RebuildModifiersContainerView(modifiers_); |
| } |
| |
| if (last_non_modifier_key != last_non_modifier_key_) { |
| last_non_modifier_key_ = last_non_modifier_key; |
| if (non_modifier_view_) { |
| RemoveChildViewT(non_modifier_view_.get()); |
| non_modifier_view_ = nullptr; |
| } |
| |
| if (last_non_modifier_key != ui::VKEY_UNKNOWN) { |
| non_modifier_view_ = |
| AddChildView(CreateKeyItemView(last_non_modifier_key_)); |
| |
| // Ensure to trigger a relayout so that the newly added |
| // `non_modifier_view_` will be visible. |
| InvalidateLayout(); |
| } |
| } |
| } |
| |
| std::vector<ui::KeyboardCode> KeyComboView::GetModifierKeycodeVector() const { |
| std::vector<ui::KeyboardCode> key_codes; |
| base::ranges::for_each( |
| modifiers_container_view_->children(), [&](views::View* view) { |
| key_codes.push_back(static_cast<KeyItemView*>(view)->key_code()); |
| }); |
| return key_codes; |
| } |
| |
| BEGIN_METADATA(KeyComboView) |
| END_METADATA |
| |
| } // namespace ash |