WSMenu.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #include "WSMenu.h"
  2. #include "WSEvent.h"
  3. #include "WSEventLoop.h"
  4. #include "WSMenuItem.h"
  5. #include "WSMenuManager.h"
  6. #include "WSScreen.h"
  7. #include "WSWindow.h"
  8. #include "WSWindowManager.h"
  9. #include <LibDraw/CharacterBitmap.h>
  10. #include <LibDraw/Font.h>
  11. #include <LibDraw/GraphicsBitmap.h>
  12. #include <LibDraw/Painter.h>
  13. #include <LibDraw/StylePainter.h>
  14. #include <WindowServer/WSClientConnection.h>
  15. #include <WindowServer/WindowClientEndpoint.h>
  16. WSMenu::WSMenu(WSClientConnection* client, int menu_id, const String& name)
  17. : CObject(client)
  18. , m_client(client)
  19. , m_menu_id(menu_id)
  20. , m_name(move(name))
  21. {
  22. }
  23. WSMenu::~WSMenu()
  24. {
  25. }
  26. const Font& WSMenu::font() const
  27. {
  28. return Font::default_font();
  29. }
  30. static const char* s_checked_bitmap_data = {
  31. " "
  32. " # "
  33. " ## "
  34. " ### "
  35. " ## ### "
  36. " ##### "
  37. " ### "
  38. " # "
  39. " "
  40. };
  41. static const char* s_submenu_arrow_bitmap_data = {
  42. " "
  43. " # "
  44. " ## "
  45. " ### "
  46. " #### "
  47. " ### "
  48. " ## "
  49. " # "
  50. " "
  51. };
  52. static CharacterBitmap* s_checked_bitmap;
  53. static const int s_checked_bitmap_width = 9;
  54. static const int s_checked_bitmap_height = 9;
  55. static const int s_submenu_arrow_bitmap_width = 9;
  56. static const int s_submenu_arrow_bitmap_height = 9;
  57. static const int s_item_icon_width = 16;
  58. static const int s_checkbox_or_icon_padding = 6;
  59. static const int s_stripe_width = 23;
  60. int WSMenu::width() const
  61. {
  62. int widest_text = 0;
  63. int widest_shortcut = 0;
  64. for (auto& item : m_items) {
  65. if (item.type() != WSMenuItem::Text)
  66. continue;
  67. int text_width = font().width(item.text());
  68. if (!item.shortcut_text().is_empty()) {
  69. int shortcut_width = font().width(item.shortcut_text());
  70. widest_shortcut = max(shortcut_width, widest_shortcut);
  71. }
  72. widest_text = max(widest_text, text_width);
  73. }
  74. int widest_item = widest_text + s_stripe_width;
  75. if (widest_shortcut)
  76. widest_item += padding_between_text_and_shortcut() + widest_shortcut;
  77. return max(widest_item, rect_in_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
  78. }
  79. int WSMenu::height() const
  80. {
  81. if (m_items.is_empty())
  82. return 0;
  83. return (m_items.last().rect().bottom() + 1) + frame_thickness();
  84. }
  85. void WSMenu::redraw()
  86. {
  87. if (!menu_window())
  88. return;
  89. draw();
  90. menu_window()->invalidate();
  91. }
  92. WSWindow& WSMenu::ensure_menu_window()
  93. {
  94. int width = this->width();
  95. if (!m_menu_window) {
  96. Point next_item_location(frame_thickness(), frame_thickness());
  97. for (auto& item : m_items) {
  98. int height = 0;
  99. if (item.type() == WSMenuItem::Text)
  100. height = item_height();
  101. else if (item.type() == WSMenuItem::Separator)
  102. height = 8;
  103. item.set_rect({ next_item_location, { width - frame_thickness() * 2, height } });
  104. next_item_location.move_by(0, height);
  105. }
  106. auto window = WSWindow::construct(*this, WSWindowType::Menu);
  107. window->set_rect(0, 0, width, height());
  108. m_menu_window = move(window);
  109. draw();
  110. }
  111. return *m_menu_window;
  112. }
  113. void WSMenu::draw()
  114. {
  115. m_theme_index_at_last_paint = WSWindowManager::the().theme_index();
  116. ASSERT(menu_window());
  117. ASSERT(menu_window()->backing_store());
  118. Painter painter(*menu_window()->backing_store());
  119. Rect rect { {}, menu_window()->size() };
  120. painter.fill_rect(rect.shrunken(6, 6), SystemColor::MenuBase);
  121. StylePainter::paint_window_frame(painter, rect);
  122. int width = this->width();
  123. if (!s_checked_bitmap)
  124. s_checked_bitmap = &CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
  125. bool has_checkable_items = false;
  126. bool has_items_with_icon = false;
  127. for (auto& item : m_items) {
  128. has_checkable_items = has_checkable_items | item.is_checkable();
  129. has_items_with_icon = has_items_with_icon | !!item.icon();
  130. }
  131. Rect stripe_rect { frame_thickness(), frame_thickness(), s_stripe_width, height() - frame_thickness() * 2 };
  132. painter.fill_rect(stripe_rect, SystemColor::MenuStripe);
  133. painter.draw_line(stripe_rect.top_right(), stripe_rect.bottom_right(), Color(SystemColor::MenuStripe).darkened());
  134. for (auto& item : m_items) {
  135. if (item.type() == WSMenuItem::Text) {
  136. Color text_color = SystemColor::WindowText;
  137. if (&item == m_hovered_item && item.is_enabled()) {
  138. painter.fill_rect(item.rect(), SystemColor::MenuSelection);
  139. painter.draw_rect(item.rect(), Color(SystemColor::MenuSelection).darkened());
  140. text_color = Color::White;
  141. } else if (!item.is_enabled()) {
  142. text_color = Color::MidGray;
  143. }
  144. Rect text_rect = item.rect().translated(stripe_rect.width() + 6, 0);
  145. if (item.is_checkable()) {
  146. Rect checkmark_rect { item.rect().x() + 7, 0, s_checked_bitmap_width, s_checked_bitmap_height };
  147. checkmark_rect.center_vertically_within(text_rect);
  148. Rect checkbox_rect = checkmark_rect.inflated(4, 4);
  149. painter.fill_rect(checkbox_rect, SystemColor::Base);
  150. StylePainter::paint_frame(painter, checkbox_rect, FrameShape::Container, FrameShadow::Sunken, 2);
  151. if (item.is_checked()) {
  152. painter.draw_bitmap(checkmark_rect.location(), *s_checked_bitmap, SystemColor::ButtonText);
  153. }
  154. } else if (item.icon()) {
  155. Rect icon_rect { item.rect().x() + 3, 0, s_item_icon_width, s_item_icon_width };
  156. icon_rect.center_vertically_within(text_rect);
  157. painter.blit(icon_rect.location(), *item.icon(), item.icon()->rect());
  158. }
  159. painter.draw_text(text_rect, item.text(), TextAlignment::CenterLeft, text_color);
  160. if (!item.shortcut_text().is_empty()) {
  161. painter.draw_text(item.rect().translated(-right_padding(), 0), item.shortcut_text(), TextAlignment::CenterRight, text_color);
  162. }
  163. if (item.is_submenu()) {
  164. static auto& submenu_arrow_bitmap = CharacterBitmap::create_from_ascii(s_submenu_arrow_bitmap_data, s_submenu_arrow_bitmap_width, s_submenu_arrow_bitmap_height).leak_ref();
  165. Rect submenu_arrow_rect {
  166. item.rect().right() - s_submenu_arrow_bitmap_width - 2,
  167. 0,
  168. s_submenu_arrow_bitmap_width,
  169. s_submenu_arrow_bitmap_height
  170. };
  171. submenu_arrow_rect.center_vertically_within(item.rect());
  172. painter.draw_bitmap(submenu_arrow_rect.location(), submenu_arrow_bitmap, SystemColor::WindowText);
  173. }
  174. } else if (item.type() == WSMenuItem::Separator) {
  175. Point p1(item.rect().translated(stripe_rect.width() + 4, 0).x(), item.rect().center().y() - 1);
  176. Point p2(width - 7, item.rect().center().y() - 1);
  177. painter.draw_line(p1, p2, SystemColor::ThreedShadow1);
  178. painter.draw_line(p1.translated(0, 1), p2.translated(0, 1), SystemColor::ThreedHighlight);
  179. }
  180. }
  181. }
  182. void WSMenu::event(CEvent& event)
  183. {
  184. if (event.type() == WSEvent::MouseMove) {
  185. ASSERT(menu_window());
  186. auto* item = item_at(static_cast<const WSMouseEvent&>(event).position());
  187. if (m_hovered_item == item)
  188. return;
  189. m_hovered_item = item;
  190. if (m_hovered_item && m_hovered_item->is_submenu()) {
  191. WSWindowManager::the().menu_manager().close_everyone_not_in_lineage(*m_hovered_item->submenu());
  192. m_hovered_item->submenu()->popup(m_hovered_item->rect().top_right().translated(menu_window()->rect().location()), true);
  193. } else {
  194. WSWindowManager::the().menu_manager().close_everyone_not_in_lineage(*this);
  195. }
  196. redraw();
  197. return;
  198. }
  199. if (event.type() == WSEvent::MouseUp) {
  200. ASSERT(menu_window());
  201. if (!m_hovered_item)
  202. return;
  203. if (m_hovered_item->is_enabled())
  204. did_activate(*m_hovered_item);
  205. clear_hovered_item();
  206. return;
  207. }
  208. CObject::event(event);
  209. }
  210. void WSMenu::clear_hovered_item()
  211. {
  212. if (!m_hovered_item)
  213. return;
  214. m_hovered_item = nullptr;
  215. redraw();
  216. }
  217. void WSMenu::did_activate(WSMenuItem& item)
  218. {
  219. if (item.type() == WSMenuItem::Type::Separator)
  220. return;
  221. if (on_item_activation)
  222. on_item_activation(item);
  223. WSWindowManager::the().menu_manager().close_bar();
  224. if (m_client)
  225. m_client->post_message(WindowClient::MenuItemActivated(m_menu_id, item.identifier()));
  226. }
  227. WSMenuItem* WSMenu::item_with_identifier(unsigned identifer)
  228. {
  229. for (auto& item : m_items) {
  230. if (item.identifier() == identifer)
  231. return &item;
  232. }
  233. return nullptr;
  234. }
  235. WSMenuItem* WSMenu::item_at(const Point& position)
  236. {
  237. for (auto& item : m_items) {
  238. if (item.rect().contains(position))
  239. return &item;
  240. }
  241. return nullptr;
  242. }
  243. void WSMenu::close()
  244. {
  245. WSWindowManager::the().menu_manager().close_menu_and_descendants(*this);
  246. }
  247. void WSMenu::redraw_if_theme_changed()
  248. {
  249. if (m_theme_index_at_last_paint != WSWindowManager::the().theme_index())
  250. redraw();
  251. }
  252. void WSMenu::popup(const Point& position, bool is_submenu)
  253. {
  254. ASSERT(!is_empty());
  255. auto& window = ensure_menu_window();
  256. redraw_if_theme_changed();
  257. const int margin = 30;
  258. Point adjusted_pos = position;
  259. if (adjusted_pos.x() + window.width() >= WSScreen::the().width() - margin) {
  260. adjusted_pos = adjusted_pos.translated(-window.width(), 0);
  261. }
  262. if (adjusted_pos.y() + window.height() >= WSScreen::the().height() - margin) {
  263. adjusted_pos = adjusted_pos.translated(0, -window.height());
  264. }
  265. window.move_to(adjusted_pos);
  266. window.set_visible(true);
  267. WSWindowManager::the().menu_manager().set_current_menu(this, is_submenu);
  268. }
  269. bool WSMenu::is_menu_ancestor_of(const WSMenu& other) const
  270. {
  271. for (auto& item : m_items) {
  272. if (!item.is_submenu())
  273. continue;
  274. auto& submenu = *const_cast<WSMenuItem&>(item).submenu();
  275. if (&submenu == &other)
  276. return true;
  277. if (submenu.is_menu_ancestor_of(other))
  278. return true;
  279. }
  280. return false;
  281. }