2019-02-11 09:47:10 +01:00
|
|
|
#include "WSMenu.h"
|
2019-04-14 05:23:37 +02:00
|
|
|
#include "WSEvent.h"
|
|
|
|
#include "WSEventLoop.h"
|
2019-06-07 11:47:19 +02:00
|
|
|
#include "WSMenuItem.h"
|
2019-05-16 01:06:21 +02:00
|
|
|
#include "WSScreen.h"
|
2019-06-07 11:47:19 +02:00
|
|
|
#include "WSWindow.h"
|
|
|
|
#include "WSWindowManager.h"
|
2019-07-18 10:15:00 +02:00
|
|
|
#include <LibDraw/CharacterBitmap.h>
|
|
|
|
#include <LibDraw/Font.h>
|
2019-08-26 18:54:44 +02:00
|
|
|
#include <LibDraw/GraphicsBitmap.h>
|
2019-07-18 10:15:00 +02:00
|
|
|
#include <LibDraw/Painter.h>
|
|
|
|
#include <LibDraw/StylePainter.h>
|
2019-06-07 11:47:19 +02:00
|
|
|
#include <WindowServer/WSAPITypes.h>
|
|
|
|
#include <WindowServer/WSClientConnection.h>
|
2019-02-11 09:47:10 +01:00
|
|
|
|
2019-05-12 14:57:15 +02:00
|
|
|
WSMenu::WSMenu(WSClientConnection* client, int menu_id, const String& name)
|
2019-08-18 12:05:33 +02:00
|
|
|
: CObject(client)
|
|
|
|
, m_client(client)
|
2019-02-14 10:44:47 +01:00
|
|
|
, m_menu_id(menu_id)
|
2019-02-12 00:52:19 +01:00
|
|
|
, m_name(move(name))
|
2019-02-11 09:47:10 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
WSMenu::~WSMenu()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
const Font& WSMenu::font() const
|
|
|
|
{
|
|
|
|
return Font::default_font();
|
|
|
|
}
|
|
|
|
|
2019-04-26 21:09:56 +02:00
|
|
|
static const char* s_checked_bitmap_data = {
|
|
|
|
" "
|
2019-05-10 22:50:42 +02:00
|
|
|
" # "
|
2019-04-26 21:09:56 +02:00
|
|
|
" ## "
|
2019-05-10 22:50:42 +02:00
|
|
|
" ### "
|
|
|
|
" ## ### "
|
|
|
|
" ##### "
|
|
|
|
" ### "
|
|
|
|
" # "
|
2019-04-26 21:09:56 +02:00
|
|
|
" "
|
|
|
|
};
|
|
|
|
|
|
|
|
static CharacterBitmap* s_checked_bitmap;
|
|
|
|
static const int s_checked_bitmap_width = 9;
|
|
|
|
static const int s_checked_bitmap_height = 9;
|
2019-08-26 18:54:44 +02:00
|
|
|
static const int s_item_icon_width = 16;
|
|
|
|
static const int s_checkbox_or_icon_padding = 6;
|
2019-08-26 20:43:30 +02:00
|
|
|
static const int s_stripe_width = 23;
|
2019-04-26 21:09:56 +02:00
|
|
|
|
2019-02-11 09:47:10 +01:00
|
|
|
int WSMenu::width() const
|
|
|
|
{
|
2019-04-29 23:41:48 +02:00
|
|
|
int widest_text = 0;
|
|
|
|
int widest_shortcut = 0;
|
2019-02-11 09:47:10 +01:00
|
|
|
for (auto& item : m_items) {
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.type() != WSMenuItem::Text)
|
2019-04-29 23:41:48 +02:00
|
|
|
continue;
|
2019-07-24 09:04:57 +02:00
|
|
|
int text_width = font().width(item.text());
|
|
|
|
if (!item.shortcut_text().is_empty()) {
|
|
|
|
int shortcut_width = font().width(item.shortcut_text());
|
2019-04-29 23:41:48 +02:00
|
|
|
widest_shortcut = max(shortcut_width, widest_shortcut);
|
2019-03-02 10:04:49 +01:00
|
|
|
}
|
2019-04-29 23:41:48 +02:00
|
|
|
widest_text = max(widest_text, text_width);
|
2019-02-11 09:47:10 +01:00
|
|
|
}
|
|
|
|
|
2019-08-26 20:43:30 +02:00
|
|
|
int widest_item = widest_text + s_stripe_width;
|
2019-04-29 23:41:48 +02:00
|
|
|
if (widest_shortcut)
|
|
|
|
widest_item += padding_between_text_and_shortcut() + widest_shortcut;
|
|
|
|
|
|
|
|
return max(widest_item, rect_in_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
|
2019-02-11 09:47:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int WSMenu::height() const
|
|
|
|
{
|
|
|
|
if (m_items.is_empty())
|
|
|
|
return 0;
|
2019-07-24 09:04:57 +02:00
|
|
|
return (m_items.last().rect().bottom() - 1) + frame_thickness() * 2;
|
2019-02-11 09:47:10 +01:00
|
|
|
}
|
|
|
|
|
2019-02-11 10:08:37 +01:00
|
|
|
void WSMenu::redraw()
|
|
|
|
{
|
2019-04-12 02:53:27 +02:00
|
|
|
if (!menu_window())
|
|
|
|
return;
|
2019-02-11 10:08:37 +01:00
|
|
|
draw();
|
|
|
|
menu_window()->invalidate();
|
|
|
|
}
|
|
|
|
|
2019-02-11 09:47:10 +01:00
|
|
|
WSWindow& WSMenu::ensure_menu_window()
|
|
|
|
{
|
2019-04-18 19:58:25 +02:00
|
|
|
int width = this->width();
|
2019-02-11 09:47:10 +01:00
|
|
|
if (!m_menu_window) {
|
2019-04-16 17:02:26 +02:00
|
|
|
Point next_item_location(frame_thickness(), frame_thickness());
|
2019-02-11 09:47:10 +01:00
|
|
|
for (auto& item : m_items) {
|
|
|
|
int height = 0;
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.type() == WSMenuItem::Text)
|
2019-02-11 09:47:10 +01:00
|
|
|
height = item_height();
|
2019-07-24 09:04:57 +02:00
|
|
|
else if (item.type() == WSMenuItem::Separator)
|
2019-04-18 19:58:25 +02:00
|
|
|
height = 8;
|
2019-07-24 09:04:57 +02:00
|
|
|
item.set_rect({ next_item_location, { width - frame_thickness() * 2, height } });
|
2019-02-11 09:47:10 +01:00
|
|
|
next_item_location.move_by(0, height);
|
|
|
|
}
|
|
|
|
|
2019-03-06 10:03:10 +01:00
|
|
|
auto window = make<WSWindow>(*this, WSWindowType::Menu);
|
2019-04-18 19:58:25 +02:00
|
|
|
window->set_rect(0, 0, width, height());
|
2019-02-11 09:47:10 +01:00
|
|
|
m_menu_window = move(window);
|
|
|
|
draw();
|
|
|
|
}
|
|
|
|
return *m_menu_window;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WSMenu::draw()
|
|
|
|
{
|
2019-02-11 10:08:37 +01:00
|
|
|
ASSERT(menu_window());
|
2019-02-20 21:59:13 +01:00
|
|
|
ASSERT(menu_window()->backing_store());
|
|
|
|
Painter painter(*menu_window()->backing_store());
|
2019-02-11 09:47:10 +01:00
|
|
|
|
2019-06-07 11:47:19 +02:00
|
|
|
Rect rect { {}, menu_window()->size() };
|
2019-06-30 09:23:16 +02:00
|
|
|
painter.fill_rect(rect.shrunken(6, 6), Color::WarmGray);
|
2019-05-11 01:31:10 +02:00
|
|
|
StylePainter::paint_window_frame(painter, rect);
|
2019-04-18 19:58:25 +02:00
|
|
|
int width = this->width();
|
2019-02-11 09:47:10 +01:00
|
|
|
|
2019-04-26 21:09:56 +02:00
|
|
|
if (!s_checked_bitmap)
|
|
|
|
s_checked_bitmap = &CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
|
|
|
|
|
2019-08-26 18:10:07 +02:00
|
|
|
bool has_checkable_items = false;
|
2019-08-26 18:54:44 +02:00
|
|
|
bool has_items_with_icon = false;
|
|
|
|
for (auto& item : m_items) {
|
2019-08-26 18:10:07 +02:00
|
|
|
has_checkable_items = has_checkable_items | item.is_checkable();
|
2019-08-26 18:54:44 +02:00
|
|
|
has_items_with_icon = has_items_with_icon | !!item.icon();
|
|
|
|
}
|
2019-08-26 18:10:07 +02:00
|
|
|
|
2019-08-26 20:43:30 +02:00
|
|
|
Rect stripe_rect { frame_thickness(), frame_thickness(), s_stripe_width, height() - frame_thickness() * 2 };
|
|
|
|
painter.fill_rect(stripe_rect, Color::from_rgb(0xffffff));
|
|
|
|
painter.draw_line(stripe_rect.top_right(), stripe_rect.bottom_right(), Color::from_rgb(0xbbb7b0));
|
|
|
|
|
2019-02-11 09:47:10 +01:00
|
|
|
for (auto& item : m_items) {
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.type() == WSMenuItem::Text) {
|
2019-02-11 09:47:10 +01:00
|
|
|
Color text_color = Color::Black;
|
2019-07-24 09:04:57 +02:00
|
|
|
if (&item == m_hovered_item) {
|
2019-08-26 20:43:30 +02:00
|
|
|
painter.fill_rect(item.rect(), Color::from_rgb(0xad714f));
|
|
|
|
painter.draw_rect(item.rect(), Color::from_rgb(0x793016));
|
2019-02-11 09:47:10 +01:00
|
|
|
text_color = Color::White;
|
|
|
|
}
|
2019-07-24 09:04:57 +02:00
|
|
|
if (!item.is_enabled())
|
2019-04-12 02:53:27 +02:00
|
|
|
text_color = Color::MidGray;
|
2019-08-26 20:43:30 +02:00
|
|
|
Rect text_rect = item.rect().translated(stripe_rect.width() + 6, 0);
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.is_checkable()) {
|
2019-08-26 20:43:30 +02:00
|
|
|
Rect checkmark_rect { item.rect().x() + 7, 0, s_checked_bitmap_width, s_checked_bitmap_height };
|
2019-08-26 18:10:07 +02:00
|
|
|
checkmark_rect.center_vertically_within(text_rect);
|
|
|
|
Rect checkbox_rect = checkmark_rect.inflated(4, 4);
|
|
|
|
painter.fill_rect(checkbox_rect, Color::White);
|
|
|
|
StylePainter::paint_frame(painter, checkbox_rect, FrameShape::Container, FrameShadow::Sunken, 2);
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.is_checked()) {
|
2019-04-26 21:09:56 +02:00
|
|
|
painter.draw_bitmap(checkmark_rect.location(), *s_checked_bitmap, Color::Black);
|
|
|
|
}
|
2019-08-26 18:54:44 +02:00
|
|
|
} else if (item.icon()) {
|
2019-08-26 20:43:30 +02:00
|
|
|
Rect icon_rect { item.rect().x() + 3, 0, s_item_icon_width, s_item_icon_width };
|
2019-08-26 18:54:44 +02:00
|
|
|
icon_rect.center_vertically_within(text_rect);
|
|
|
|
painter.blit(icon_rect.location(), *item.icon(), item.icon()->rect());
|
2019-04-26 21:09:56 +02:00
|
|
|
}
|
2019-07-24 09:04:57 +02:00
|
|
|
painter.draw_text(text_rect, item.text(), TextAlignment::CenterLeft, text_color);
|
|
|
|
if (!item.shortcut_text().is_empty()) {
|
|
|
|
painter.draw_text(item.rect().translated(-right_padding(), 0), item.shortcut_text(), TextAlignment::CenterRight, text_color);
|
2019-03-02 10:04:49 +01:00
|
|
|
}
|
2019-07-24 09:04:57 +02:00
|
|
|
} else if (item.type() == WSMenuItem::Separator) {
|
2019-08-26 20:43:30 +02:00
|
|
|
Point p1(item.rect().translated(stripe_rect.width() + 4, 0).x(), item.rect().center().y());
|
|
|
|
Point p2(width - 7, item.rect().center().y());
|
2019-02-11 09:47:10 +01:00
|
|
|
painter.draw_line(p1, p2, Color::MidGray);
|
2019-04-18 19:58:25 +02:00
|
|
|
painter.draw_line(p1.translated(0, 1), p2.translated(0, 1), Color::White);
|
2019-02-11 09:47:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-14 05:23:37 +02:00
|
|
|
void WSMenu::event(CEvent& event)
|
2019-02-11 09:47:10 +01:00
|
|
|
{
|
2019-04-14 05:23:37 +02:00
|
|
|
if (event.type() == WSEvent::MouseMove) {
|
2019-08-19 14:32:18 +03:00
|
|
|
ASSERT(menu_window());
|
2019-04-14 05:23:37 +02:00
|
|
|
auto* item = item_at(static_cast<const WSMouseEvent&>(event).position());
|
2019-02-11 10:08:37 +01:00
|
|
|
if (!item || m_hovered_item == item)
|
|
|
|
return;
|
|
|
|
m_hovered_item = item;
|
|
|
|
redraw();
|
2019-02-11 10:55:02 +01:00
|
|
|
return;
|
2019-02-11 10:08:37 +01:00
|
|
|
}
|
2019-02-11 10:55:02 +01:00
|
|
|
|
2019-04-14 05:23:37 +02:00
|
|
|
if (event.type() == WSEvent::MouseUp) {
|
2019-08-19 14:32:18 +03:00
|
|
|
ASSERT(menu_window());
|
2019-02-11 10:55:02 +01:00
|
|
|
if (!m_hovered_item)
|
|
|
|
return;
|
2019-04-12 02:53:27 +02:00
|
|
|
if (m_hovered_item->is_enabled())
|
|
|
|
did_activate(*m_hovered_item);
|
2019-02-11 11:06:41 +01:00
|
|
|
clear_hovered_item();
|
2019-02-11 10:55:02 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-04-22 01:15:47 +02:00
|
|
|
|
|
|
|
CObject::event(event);
|
2019-02-11 10:55:02 +01:00
|
|
|
}
|
|
|
|
|
2019-02-11 11:06:41 +01:00
|
|
|
void WSMenu::clear_hovered_item()
|
|
|
|
{
|
|
|
|
if (!m_hovered_item)
|
|
|
|
return;
|
|
|
|
m_hovered_item = nullptr;
|
|
|
|
redraw();
|
|
|
|
}
|
|
|
|
|
2019-02-11 10:55:02 +01:00
|
|
|
void WSMenu::did_activate(WSMenuItem& item)
|
|
|
|
{
|
|
|
|
if (on_item_activation)
|
|
|
|
on_item_activation(item);
|
2019-02-12 10:08:35 +01:00
|
|
|
|
2019-02-11 13:59:26 +01:00
|
|
|
close();
|
2019-02-12 10:08:35 +01:00
|
|
|
|
2019-02-15 09:17:18 +01:00
|
|
|
WSAPI_ServerMessage message;
|
|
|
|
message.type = WSAPI_ServerMessage::Type::MenuItemActivated;
|
2019-02-14 10:44:47 +01:00
|
|
|
message.menu.menu_id = m_menu_id;
|
|
|
|
message.menu.identifier = item.identifier();
|
2019-02-12 10:08:35 +01:00
|
|
|
|
2019-02-17 09:06:47 +01:00
|
|
|
if (m_client)
|
|
|
|
m_client->post_message(message);
|
2019-02-11 10:08:37 +01:00
|
|
|
}
|
|
|
|
|
2019-04-12 02:53:27 +02:00
|
|
|
WSMenuItem* WSMenu::item_with_identifier(unsigned identifer)
|
|
|
|
{
|
|
|
|
for (auto& item : m_items) {
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.identifier() == identifer)
|
|
|
|
return &item;
|
2019-04-12 02:53:27 +02:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-02-11 10:08:37 +01:00
|
|
|
WSMenuItem* WSMenu::item_at(const Point& position)
|
|
|
|
{
|
|
|
|
for (auto& item : m_items) {
|
2019-07-24 09:04:57 +02:00
|
|
|
if (item.rect().contains(position))
|
|
|
|
return &item;
|
2019-02-11 09:47:10 +01:00
|
|
|
}
|
2019-02-11 10:08:37 +01:00
|
|
|
return nullptr;
|
2019-02-11 09:47:10 +01:00
|
|
|
}
|
2019-02-11 13:59:26 +01:00
|
|
|
|
|
|
|
void WSMenu::close()
|
|
|
|
{
|
|
|
|
WSWindowManager::the().close_menu(*this);
|
2019-04-12 17:10:30 +02:00
|
|
|
if (menu_window())
|
|
|
|
menu_window()->set_visible(false);
|
|
|
|
}
|
|
|
|
|
2019-05-16 01:06:21 +02:00
|
|
|
void WSMenu::popup(const Point& position)
|
2019-04-12 17:10:30 +02:00
|
|
|
{
|
|
|
|
ASSERT(!is_empty());
|
2019-05-16 01:06:21 +02:00
|
|
|
|
2019-04-12 17:10:30 +02:00
|
|
|
auto& window = ensure_menu_window();
|
2019-05-16 01:06:21 +02:00
|
|
|
const int margin = 30;
|
|
|
|
Point adjusted_pos = position;
|
|
|
|
if (adjusted_pos.x() + window.width() >= WSScreen::the().width() - margin) {
|
|
|
|
adjusted_pos = adjusted_pos.translated(-window.width(), 0);
|
|
|
|
}
|
|
|
|
if (adjusted_pos.y() + window.height() >= WSScreen::the().height() - margin) {
|
|
|
|
adjusted_pos = adjusted_pos.translated(0, -window.height());
|
|
|
|
}
|
|
|
|
|
|
|
|
window.move_to(adjusted_pos);
|
2019-04-12 17:10:30 +02:00
|
|
|
window.set_visible(true);
|
2019-04-14 02:16:49 +02:00
|
|
|
WSWindowManager::the().set_current_menu(this);
|
2019-04-12 17:10:30 +02:00
|
|
|
}
|