Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#pragma once
8
9#include <AK/DeprecatedString.h>
10#include <AK/WeakPtr.h>
11#include <LibCore/Object.h>
12#include <LibGfx/Font/Font.h>
13#include <LibGfx/Font/FontDatabase.h>
14#include <LibGfx/Forward.h>
15#include <LibGfx/Rect.h>
16#include <WindowServer/Cursor.h>
17#include <WindowServer/Event.h>
18#include <WindowServer/MenuItem.h>
19
20namespace WindowServer {
21
22class ConnectionFromClient;
23class Menubar;
24class Window;
25
26class Menu final : public Core::Object {
27 C_OBJECT(Menu);
28
29public:
30 virtual ~Menu() override = default;
31
32 ConnectionFromClient* client() { return m_client; }
33 ConnectionFromClient const* client() const { return m_client; }
34 int menu_id() const { return m_menu_id; }
35
36 bool is_open() const;
37
38 u32 alt_shortcut_character() const { return m_alt_shortcut_character; }
39
40 bool is_empty() const { return m_items.is_empty(); }
41 size_t item_count() const { return m_items.size(); }
42 MenuItem const& item(size_t index) const { return *m_items.at(index); }
43 MenuItem& item(size_t index) { return *m_items.at(index); }
44
45 MenuItem* item_by_identifier(unsigned identifier)
46 {
47 MenuItem* found_item = nullptr;
48 for_each_item([&](auto& item) {
49 if (item.identifier() == identifier) {
50 found_item = &item;
51 return IterationDecision::Break;
52 }
53 return IterationDecision::Continue;
54 });
55 return found_item;
56 }
57
58 void update_alt_shortcuts_for_items();
59 void add_item(NonnullOwnPtr<MenuItem>);
60
61 DeprecatedString const& name() const { return m_name; }
62
63 template<typename Callback>
64 IterationDecision for_each_item(Callback callback)
65 {
66 for (auto& item : m_items) {
67 IterationDecision decision = callback(*item);
68 if (decision != IterationDecision::Continue)
69 return decision;
70 }
71 return IterationDecision::Continue;
72 }
73
74 Gfx::IntRect rect_in_window_menubar() const { return m_rect_in_window_menubar; }
75 void set_rect_in_window_menubar(Gfx::IntRect const& rect) { m_rect_in_window_menubar = rect; }
76
77 Gfx::IntPoint unadjusted_position() const { return m_unadjusted_position; }
78 void set_unadjusted_position(Gfx::IntPoint position) { m_unadjusted_position = position; }
79
80 Window* menu_window() { return m_menu_window.ptr(); }
81 Window& ensure_menu_window(Gfx::IntPoint);
82
83 // Invalidates the menu window so that it gets rebuilt the next time it's showed.
84 void invalidate_menu_window();
85
86 Window* window_menu_of() { return m_window_menu_of; }
87 void set_window_menu_of(Window& window) { m_window_menu_of = window; }
88 bool is_window_menu_open() const { return m_is_window_menu_open; }
89 void set_window_menu_open(bool is_open) { m_is_window_menu_open = is_open; }
90
91 bool activate_default();
92
93 int content_width() const;
94
95 int item_height() const;
96 static constexpr int frame_thickness() { return 2; }
97 static constexpr int horizontal_padding() { return left_padding() + right_padding(); }
98 static constexpr int left_padding() { return 14; }
99 static constexpr int right_padding() { return 14; }
100
101 void draw();
102 void draw(MenuItem const&, bool = false);
103 Gfx::Font const& font() const;
104
105 MenuItem* item_with_identifier(unsigned);
106 bool remove_item_with_identifier(unsigned);
107 void redraw();
108 void redraw(MenuItem const&);
109
110 MenuItem* hovered_item() const;
111 int hovered_item_index() const { return m_hovered_item_index; };
112
113 void set_hovered_index(int index, bool make_input = false);
114
115 void clear_hovered_item();
116
117 Function<void(MenuItem&)> on_item_activation;
118
119 void close();
120
121 void set_visible(bool);
122
123 void popup(Gfx::IntPoint);
124 void do_popup(Gfx::IntPoint, bool make_input, bool as_submenu = false);
125 void open_button_menu(Gfx::IntPoint position, Gfx::IntRect const& button_rect);
126
127 bool is_menu_ancestor_of(Menu const&) const;
128
129 void redraw_if_theme_changed();
130
131 bool is_scrollable() const { return m_scrollable; }
132 int scroll_offset() const { return m_scroll_offset; }
133
134 void descend_into_submenu_at_hovered_item();
135 void open_hovered_item(bool leave_menu_open);
136
137 Vector<size_t> const* items_with_alt_shortcut(u32 alt_shortcut) const;
138
139private:
140 Menu(ConnectionFromClient*, int menu_id, DeprecatedString name);
141
142 virtual void event(Core::Event&) override;
143
144 void handle_mouse_move_event(MouseEvent const&);
145 size_t visible_item_count() const;
146 Gfx::IntRect stripe_rect();
147
148 int item_index_at(Gfx::IntPoint);
149 static constexpr int padding_between_text_and_shortcut() { return 50; }
150 void did_activate(MenuItem&, bool leave_menu_open);
151 void update_for_new_hovered_item(bool make_input = false);
152
153 void start_activation_animation(MenuItem&);
154
155 ConnectionFromClient* m_client { nullptr };
156 int m_menu_id { 0 };
157 DeprecatedString m_name;
158 u32 m_alt_shortcut_character { 0 };
159 Gfx::IntRect m_rect_in_window_menubar;
160 Gfx::IntPoint m_unadjusted_position;
161 Vector<NonnullOwnPtr<MenuItem>> m_items;
162 RefPtr<Window> m_menu_window;
163
164 WeakPtr<Window> m_window_menu_of;
165 bool m_is_window_menu_open = { false };
166 Gfx::IntPoint m_last_position_in_hover;
167 int m_theme_index_at_last_paint { -1 };
168 int m_hovered_item_index { -1 };
169
170 bool m_scrollable { false };
171 int m_scroll_offset { 0 };
172 int m_max_scroll_offset { 0 };
173
174 HashMap<u32, Vector<size_t>> m_alt_shortcut_character_to_item_indices;
175};
176
177u32 find_ampersand_shortcut_character(StringView);
178
179}