2020-01-18 05:38:21 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
|
|
|
*
|
2021-04-22 05:24:48 -03:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 05:38:21 -03:00
|
|
|
*/
|
|
|
|
|
|
2019-02-11 06:47:10 -02:00
|
|
|
#pragma once
|
|
|
|
|
|
2019-07-24 04:04:57 -03:00
|
|
|
#include <AK/NonnullOwnPtrVector.h>
|
2020-02-06 07:56:38 -03:00
|
|
|
#include <AK/String.h>
|
2019-02-12 05:39:19 -02:00
|
|
|
#include <AK/WeakPtr.h>
|
2020-02-06 11:04:03 -03:00
|
|
|
#include <LibCore/Object.h>
|
2020-03-10 04:56:07 -03:00
|
|
|
#include <LibGfx/Font.h>
|
2020-12-29 14:25:13 -03:00
|
|
|
#include <LibGfx/FontDatabase.h>
|
2020-02-14 20:10:34 -03:00
|
|
|
#include <LibGfx/Forward.h>
|
2020-02-06 08:04:00 -03:00
|
|
|
#include <LibGfx/Rect.h>
|
2020-02-06 16:03:37 -03:00
|
|
|
#include <WindowServer/Cursor.h>
|
2021-07-29 07:14:12 -03:00
|
|
|
#include <WindowServer/Event.h>
|
2020-02-06 16:03:37 -03:00
|
|
|
#include <WindowServer/MenuItem.h>
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
namespace WindowServer {
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
class ClientConnection;
|
2021-04-13 11:18:20 -03:00
|
|
|
class Menubar;
|
2021-07-29 07:14:12 -03:00
|
|
|
class Window;
|
2020-02-06 16:03:37 -03:00
|
|
|
|
|
|
|
|
class Menu final : public Core::Object {
|
2021-04-09 08:48:26 -03:00
|
|
|
C_OBJECT(Menu);
|
|
|
|
|
|
2019-02-11 06:47:10 -02:00
|
|
|
public:
|
2021-04-05 18:03:55 -03:00
|
|
|
Menu(ClientConnection*, int menu_id, String name);
|
2020-02-06 16:03:37 -03:00
|
|
|
virtual ~Menu() override;
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
ClientConnection* client() { return m_client; }
|
|
|
|
|
const ClientConnection* client() const { return m_client; }
|
2019-02-11 21:52:19 -02:00
|
|
|
int menu_id() const { return m_menu_id; }
|
|
|
|
|
|
2021-05-17 17:01:05 -03:00
|
|
|
bool is_open() const;
|
|
|
|
|
|
2021-04-05 18:03:55 -03:00
|
|
|
u32 alt_shortcut_character() const { return m_alt_shortcut_character; }
|
|
|
|
|
|
2019-02-12 11:28:39 -02:00
|
|
|
bool is_empty() const { return m_items.is_empty(); }
|
2021-05-09 06:09:58 -03:00
|
|
|
size_t item_count() const { return m_items.size(); }
|
|
|
|
|
const MenuItem& item(size_t index) const { return m_items.at(index); }
|
|
|
|
|
MenuItem& item(size_t index) { return m_items.at(index); }
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2021-04-28 00:24:32 -03:00
|
|
|
MenuItem* item_by_identifier(unsigned identifier)
|
|
|
|
|
{
|
|
|
|
|
MenuItem* found_item = nullptr;
|
|
|
|
|
for_each_item([&](auto& item) {
|
|
|
|
|
if (item.identifier() == identifier) {
|
|
|
|
|
found_item = &item;
|
|
|
|
|
return IterationDecision::Break;
|
|
|
|
|
}
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
return found_item;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-09 11:33:37 -03:00
|
|
|
void add_item(NonnullOwnPtr<MenuItem>);
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2021-05-09 06:09:58 -03:00
|
|
|
String const& name() const { return m_name; }
|
2019-02-11 06:47:10 -02:00
|
|
|
|
|
|
|
|
template<typename Callback>
|
2021-04-28 00:24:32 -03:00
|
|
|
IterationDecision for_each_item(Callback callback)
|
2019-02-11 06:47:10 -02:00
|
|
|
{
|
2021-04-28 00:24:32 -03:00
|
|
|
for (auto& item : m_items) {
|
|
|
|
|
IterationDecision decision = callback(item);
|
|
|
|
|
if (decision != IterationDecision::Continue)
|
|
|
|
|
return decision;
|
|
|
|
|
}
|
|
|
|
|
return IterationDecision::Continue;
|
2019-02-11 06:47:10 -02:00
|
|
|
}
|
|
|
|
|
|
2021-03-25 17:01:19 -03:00
|
|
|
Gfx::IntRect rect_in_window_menubar() const { return m_rect_in_window_menubar; }
|
|
|
|
|
void set_rect_in_window_menubar(const Gfx::IntRect& rect) { m_rect_in_window_menubar = rect; }
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
Window* menu_window() { return m_menu_window.ptr(); }
|
2021-06-27 17:40:51 -03:00
|
|
|
Window& ensure_menu_window(Gfx::IntPoint const&);
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
Window* window_menu_of() { return m_window_menu_of; }
|
AK: Make RefPtr, NonnullRefPtr, WeakPtr thread safe
This makes most operations thread safe, especially so that they
can safely be used in the Kernel. This includes obtaining a strong
reference from a weak reference, which now requires an explicit
call to WeakPtr::strong_ref(). Another major change is that
Weakable::make_weak_ref() may require the explicit target type.
Previously we used reinterpret_cast in WeakPtr, assuming that it
can be properly converted. But WeakPtr does not necessarily have
the knowledge to be able to do this. Instead, we now ask the class
itself to deliver a WeakPtr to the type that we want.
Also, WeakLink is no longer specific to a target type. The reason
for this is that we want to be able to safely convert e.g. WeakPtr<T>
to WeakPtr<U>, and before this we just reinterpret_cast the internal
WeakLink<T> to WeakLink<U>, which is a bold assumption that it would
actually produce the correct code. Instead, WeakLink now operates
on just a raw pointer and we only make those constructors/operators
available if we can verify that it can be safely cast.
In order to guarantee thread safety, we now use the least significant
bit in the pointer for locking purposes. This also means that only
properly aligned pointers can be used.
2020-09-29 19:26:13 -03:00
|
|
|
void set_window_menu_of(Window& window) { m_window_menu_of = window; }
|
2021-05-09 06:09:58 -03:00
|
|
|
bool is_window_menu_open() const { return m_is_window_menu_open; }
|
2020-01-04 10:14:36 -03:00
|
|
|
void set_window_menu_open(bool is_open) { m_is_window_menu_open = is_open; }
|
|
|
|
|
|
2020-07-07 15:09:18 -03:00
|
|
|
bool activate_default();
|
|
|
|
|
|
2020-01-19 17:28:38 -03:00
|
|
|
int content_width() const;
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2021-05-09 06:09:58 -03:00
|
|
|
static constexpr int item_height() { return 22; }
|
|
|
|
|
static constexpr int frame_thickness() { return 2; }
|
|
|
|
|
static constexpr int horizontal_padding() { return left_padding() + right_padding(); }
|
|
|
|
|
static constexpr int left_padding() { return 14; }
|
|
|
|
|
static constexpr int right_padding() { return 14; }
|
2019-02-11 06:47:10 -02:00
|
|
|
|
|
|
|
|
void draw();
|
2021-07-18 17:38:34 -03:00
|
|
|
void draw(MenuItem const&, bool = false);
|
2020-02-06 07:56:38 -03:00
|
|
|
const Gfx::Font& font() const;
|
2019-02-11 06:47:10 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
MenuItem* item_with_identifier(unsigned);
|
2019-02-11 07:08:37 -02:00
|
|
|
void redraw();
|
2021-07-18 17:38:34 -03:00
|
|
|
void redraw(MenuItem const&);
|
2019-02-11 07:08:37 -02:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
MenuItem* hovered_item() const;
|
2020-05-09 00:57:22 -03:00
|
|
|
|
2021-04-17 13:16:54 -03:00
|
|
|
void set_hovered_index(int index, bool make_input = false);
|
2020-05-09 00:57:22 -03:00
|
|
|
|
2019-02-11 08:06:41 -02:00
|
|
|
void clear_hovered_item();
|
|
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
Function<void(MenuItem&)> on_item_activation;
|
2019-02-11 07:55:02 -02:00
|
|
|
|
2019-02-11 10:59:26 -02:00
|
|
|
void close();
|
|
|
|
|
|
2021-04-05 09:32:34 -03:00
|
|
|
void set_visible(bool);
|
|
|
|
|
|
2020-06-10 05:57:59 -03:00
|
|
|
void popup(const Gfx::IntPoint&);
|
2021-03-26 05:22:31 -03:00
|
|
|
void do_popup(const Gfx::IntPoint&, bool make_input, bool as_submenu = false);
|
2019-04-12 12:10:30 -03:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
bool is_menu_ancestor_of(const Menu&) const;
|
2019-11-11 06:43:03 -03:00
|
|
|
|
2019-12-23 16:24:26 -03:00
|
|
|
void redraw_if_theme_changed();
|
|
|
|
|
|
2020-01-19 17:28:38 -03:00
|
|
|
bool is_scrollable() const { return m_scrollable; }
|
|
|
|
|
int scroll_offset() const { return m_scroll_offset; }
|
|
|
|
|
|
2020-05-09 00:57:22 -03:00
|
|
|
void descend_into_submenu_at_hovered_item();
|
2021-03-26 10:45:15 -03:00
|
|
|
void open_hovered_item(bool leave_menu_open);
|
2020-05-09 00:57:22 -03:00
|
|
|
|
2021-04-09 11:33:37 -03:00
|
|
|
const Vector<size_t>* items_with_alt_shortcut(u32 alt_shortcut) const;
|
|
|
|
|
|
2019-02-11 06:47:10 -02:00
|
|
|
private:
|
2020-02-02 08:34:39 -03:00
|
|
|
virtual void event(Core::Event&) override;
|
2019-03-06 06:03:10 -03:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
void handle_mouse_move_event(const MouseEvent&);
|
2021-05-09 06:09:58 -03:00
|
|
|
size_t visible_item_count() const;
|
2021-07-18 17:38:34 -03:00
|
|
|
Gfx::IntRect stripe_rect();
|
2020-01-19 17:28:38 -03:00
|
|
|
|
2020-06-10 05:57:59 -03:00
|
|
|
int item_index_at(const Gfx::IntPoint&);
|
2021-05-09 06:09:58 -03:00
|
|
|
static constexpr int padding_between_text_and_shortcut() { return 50; }
|
2021-03-26 10:45:15 -03:00
|
|
|
void did_activate(MenuItem&, bool leave_menu_open);
|
2020-07-15 13:13:13 -03:00
|
|
|
void update_for_new_hovered_item(bool make_input = false);
|
WSMenu: Support menu navigation through key presses
Add event handling for key presses for navigating a menu. The currently
hovered menu item is tracked through an index which is either
incremented or decremented on up or down arrow key presses, changing the
hovered item.
Whenever there is a mouse move event, we ensure that the current index
matches the currently hovered item so that the mouse and keyboard do not
get out of sync.
If the right key is pressed, and we are on a submenu menu item, we
'enter' that submenu. While we are currently in a submenu, we forward
all keypress events to that submenu for handling. This allows us to
traverse the heirachy of a menu. While in a submenu, if the left key is
pressed, we leave that submenu and start handling the keypresses
ourselves again.
There is currently a small issue where the mouse hover and key hover can
get out of sync. The mouse can be traversing a submenu, but the parent
menu has no idea that the mouse has 'entered' a submenu, so will handle
the key presses itself, instead of forwarding them to the submenu. One
potential fix for this is for a menu to tell its menu parent that the
submenu is being traversed.
2020-01-02 22:11:49 -03:00
|
|
|
|
2021-03-27 17:34:38 -03:00
|
|
|
void start_activation_animation(MenuItem&);
|
|
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
ClientConnection* m_client { nullptr };
|
2019-02-11 21:52:19 -02:00
|
|
|
int m_menu_id { 0 };
|
2019-02-11 06:47:10 -02:00
|
|
|
String m_name;
|
2021-04-05 18:03:55 -03:00
|
|
|
u32 m_alt_shortcut_character { 0 };
|
2021-03-25 17:01:19 -03:00
|
|
|
Gfx::IntRect m_rect_in_window_menubar;
|
2020-02-06 16:03:37 -03:00
|
|
|
NonnullOwnPtrVector<MenuItem> m_items;
|
|
|
|
|
RefPtr<Window> m_menu_window;
|
WSMenu: Support menu navigation through key presses
Add event handling for key presses for navigating a menu. The currently
hovered menu item is tracked through an index which is either
incremented or decremented on up or down arrow key presses, changing the
hovered item.
Whenever there is a mouse move event, we ensure that the current index
matches the currently hovered item so that the mouse and keyboard do not
get out of sync.
If the right key is pressed, and we are on a submenu menu item, we
'enter' that submenu. While we are currently in a submenu, we forward
all keypress events to that submenu for handling. This allows us to
traverse the heirachy of a menu. While in a submenu, if the left key is
pressed, we leave that submenu and start handling the keypresses
ourselves again.
There is currently a small issue where the mouse hover and key hover can
get out of sync. The mouse can be traversing a submenu, but the parent
menu has no idea that the mouse has 'entered' a submenu, so will handle
the key presses itself, instead of forwarding them to the submenu. One
potential fix for this is for a menu to tell its menu parent that the
submenu is being traversed.
2020-01-02 22:11:49 -03:00
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
WeakPtr<Window> m_window_menu_of;
|
2020-01-04 10:14:36 -03:00
|
|
|
bool m_is_window_menu_open = { false };
|
2020-06-10 05:57:59 -03:00
|
|
|
Gfx::IntPoint m_last_position_in_hover;
|
2019-12-23 16:24:26 -03:00
|
|
|
int m_theme_index_at_last_paint { -1 };
|
2020-01-18 04:56:51 -03:00
|
|
|
int m_hovered_item_index { -1 };
|
2020-01-19 17:28:38 -03:00
|
|
|
|
|
|
|
|
bool m_scrollable { false };
|
|
|
|
|
int m_scroll_offset { 0 };
|
|
|
|
|
int m_max_scroll_offset { 0 };
|
2021-04-09 11:33:37 -03:00
|
|
|
|
2021-04-29 17:23:52 -03:00
|
|
|
HashMap<u32, Vector<size_t>> m_alt_shortcut_character_to_item_indices;
|
2019-02-11 06:47:10 -02:00
|
|
|
};
|
2020-02-06 16:03:37 -03:00
|
|
|
|
2021-04-09 08:48:26 -03:00
|
|
|
u32 find_ampersand_shortcut_character(const StringView&);
|
|
|
|
|
|
2020-02-06 16:03:37 -03:00
|
|
|
}
|