WSWindowManager.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. #include "WSWindowManager.h"
  2. #include "WSWindow.h"
  3. #include "WSScreen.h"
  4. #include "WSMessageLoop.h"
  5. #include <SharedGraphics/Font.h>
  6. #include <SharedGraphics/Painter.h>
  7. #include <SharedGraphics/CharacterBitmap.h>
  8. #include <AK/StdLibExtras.h>
  9. #include <LibC/errno_numbers.h>
  10. #include "WSMenu.h"
  11. #include "WSMenuBar.h"
  12. #include "WSMenuItem.h"
  13. #include <WindowServer/WSClientConnection.h>
  14. #include <sys/ioctl.h>
  15. #include <unistd.h>
  16. #include <stdio.h>
  17. #include <time.h>
  18. #ifdef KERNEL
  19. #include <Kernel/ProcFS.h>
  20. #endif
  21. //#define DEBUG_COUNTERS
  22. //#define DEBUG_WID_IN_TITLE_BAR
  23. static const int window_titlebar_height = 16;
  24. static inline Rect title_bar_rect(const Rect& window)
  25. {
  26. return {
  27. window.x() - 1,
  28. window.y() - window_titlebar_height,
  29. window.width() + 2,
  30. window_titlebar_height
  31. };
  32. }
  33. static inline Rect title_bar_text_rect(const Rect& window)
  34. {
  35. auto titlebar_rect = title_bar_rect(window);
  36. return {
  37. titlebar_rect.x() + 2,
  38. titlebar_rect.y(),
  39. titlebar_rect.width() - 4,
  40. titlebar_rect.height()
  41. };
  42. }
  43. static inline Rect close_button_rect_for_window(const Rect& window_rect)
  44. {
  45. auto titlebar_inner_rect = title_bar_text_rect(window_rect);
  46. int close_button_margin = 1;
  47. int close_button_size = titlebar_inner_rect.height() - close_button_margin * 2;
  48. return Rect {
  49. titlebar_inner_rect.right() - close_button_size + 1,
  50. titlebar_inner_rect.top() + close_button_margin,
  51. close_button_size,
  52. close_button_size - 1
  53. };
  54. }
  55. static inline Rect border_window_rect(const Rect& window)
  56. {
  57. auto titlebar_rect = title_bar_rect(window);
  58. return { titlebar_rect.x() - 1,
  59. titlebar_rect.y() - 1,
  60. titlebar_rect.width() + 2,
  61. window_titlebar_height + window.height() + 3
  62. };
  63. }
  64. static inline Rect outer_window_rect(const Rect& window)
  65. {
  66. auto rect = border_window_rect(window);
  67. rect.inflate(2, 2);
  68. return rect;
  69. }
  70. static WSWindowManager* s_the;
  71. WSWindowManager& WSWindowManager::the()
  72. {
  73. ASSERT(s_the);
  74. return *s_the;
  75. }
  76. static const char* cursor_bitmap_inner_ascii = {
  77. " # "
  78. " ## "
  79. " ### "
  80. " #### "
  81. " ##### "
  82. " ###### "
  83. " ####### "
  84. " ######## "
  85. " ######### "
  86. " ########## "
  87. " ###### "
  88. " ## ## "
  89. " # ## "
  90. " ## "
  91. " ## "
  92. " ## "
  93. " "
  94. };
  95. static const char* cursor_bitmap_outer_ascii = {
  96. "## "
  97. "# # "
  98. "# # "
  99. "# # "
  100. "# # "
  101. "# # "
  102. "# # "
  103. "# # "
  104. "# # "
  105. "# # "
  106. "# #### "
  107. "# ## # "
  108. "# # # # "
  109. "## # # "
  110. " # # "
  111. " # # "
  112. " ## "
  113. };
  114. void WSWindowManager::flip_buffers()
  115. {
  116. swap(m_front_bitmap, m_back_bitmap);
  117. swap(m_front_painter, m_back_painter);
  118. if (m_framebuffer_fd != -1) {
  119. int new_y_offset = m_buffers_are_flipped ? 0 : m_screen_rect.height();
  120. int rc = ioctl(m_framebuffer_fd, 1982, new_y_offset);
  121. ASSERT(rc == 0);
  122. }
  123. m_buffers_are_flipped = !m_buffers_are_flipped;
  124. }
  125. WSWindowManager::WSWindowManager()
  126. : m_screen(WSScreen::the())
  127. , m_screen_rect(m_screen.rect())
  128. , m_flash_flush(false)
  129. {
  130. s_the = this;
  131. #ifndef DEBUG_COUNTERS
  132. (void)m_compose_count;
  133. (void)m_flush_count;
  134. #endif
  135. auto size = m_screen_rect.size();
  136. m_front_bitmap = GraphicsBitmap::create_wrapper(size, m_screen.scanline(0));
  137. m_back_bitmap = GraphicsBitmap::create_wrapper(size, m_screen.scanline(size.height()));
  138. m_front_painter = make<Painter>(*m_front_bitmap);
  139. m_back_painter = make<Painter>(*m_back_bitmap);
  140. m_font = Font::default_font();
  141. m_front_painter->set_font(font());
  142. m_back_painter->set_font(font());
  143. m_background_color = Color(50, 50, 50);
  144. m_active_window_border_color = Color(110, 34, 9);
  145. m_active_window_border_color2 = Color(244, 202, 158);
  146. m_active_window_title_color = Color::White;
  147. m_inactive_window_border_color = Color(128, 128, 128);
  148. m_inactive_window_border_color2 = Color(192, 192, 192);
  149. m_inactive_window_title_color = Color(213, 208, 199);
  150. m_dragging_window_border_color = Color(161, 50, 13);
  151. m_dragging_window_border_color2 = Color(250, 220, 187);
  152. m_dragging_window_title_color = Color::White;
  153. m_cursor_bitmap_inner = CharacterBitmap::create_from_ascii(cursor_bitmap_inner_ascii, 12, 17);
  154. m_cursor_bitmap_outer = CharacterBitmap::create_from_ascii(cursor_bitmap_outer_ascii, 12, 17);
  155. m_wallpaper_path = "/res/wallpapers/cool.rgb";
  156. m_wallpaper = GraphicsBitmap::load_from_file(m_wallpaper_path, m_screen_rect.size());
  157. #ifdef KERNEL
  158. ProcFS::the().add_sys_bool("wm_flash_flush", m_flash_flush);
  159. ProcFS::the().add_sys_string("wm_wallpaper", m_wallpaper_path, [this] {
  160. m_wallpaper = GraphicsBitmap::load_from_file(m_wallpaper_path, m_screen_rect.size());
  161. invalidate(m_screen_rect);
  162. });
  163. #endif
  164. m_menu_selection_color = Color(0x84351a);
  165. {
  166. byte system_menu_name[] = { 0xf8, 0 };
  167. m_system_menu = make<WSMenu>(-1, -1, String((const char*)system_menu_name));
  168. m_system_menu->add_item(make<WSMenuItem>(0, "Launch Terminal"));
  169. m_system_menu->add_item(make<WSMenuItem>(WSMenuItem::Separator));
  170. m_system_menu->add_item(make<WSMenuItem>(1, "Hello again"));
  171. m_system_menu->add_item(make<WSMenuItem>(2, "To all my friends"));
  172. m_system_menu->add_item(make<WSMenuItem>(3, "Together we can play some rock&roll"));
  173. m_system_menu->add_item(make<WSMenuItem>(WSMenuItem::Separator));
  174. m_system_menu->add_item(make<WSMenuItem>(4, "About..."));
  175. m_system_menu->on_item_activation = [] (WSMenuItem& item) {
  176. if (item.identifier() == 0) {
  177. if (fork() == 0) {
  178. execl("/bin/Terminal", "/bin/Terminal", nullptr);
  179. ASSERT_NOT_REACHED();
  180. }
  181. return;
  182. }
  183. if (item.identifier() == 4) {
  184. if (fork() == 0) {
  185. execl("/bin/About", "/bin/About", nullptr);
  186. ASSERT_NOT_REACHED();
  187. }
  188. return;
  189. }
  190. dbgprintf("WSMenu 1 item activated: '%s'\n", item.text().characters());
  191. };
  192. }
  193. // NOTE: This ensures that the system menu has the correct dimensions.
  194. set_current_menubar(nullptr);
  195. WSMessageLoop::the().start_timer(300, [this] {
  196. static time_t last_update_time;
  197. time_t now = time(nullptr);
  198. if (now != last_update_time) {
  199. invalidate(menubar_rect());
  200. last_update_time = now;
  201. }
  202. });
  203. invalidate();
  204. compose();
  205. }
  206. WSWindowManager::~WSWindowManager()
  207. {
  208. }
  209. template<typename Callback>
  210. void WSWindowManager::for_each_active_menubar_menu(Callback callback)
  211. {
  212. callback(*m_system_menu);
  213. if (m_current_menubar)
  214. m_current_menubar->for_each_menu(callback);
  215. }
  216. int WSWindowManager::menubar_menu_margin() const
  217. {
  218. return 16;
  219. }
  220. void WSWindowManager::set_current_menubar(WSMenuBar* menubar)
  221. {
  222. if (menubar)
  223. m_current_menubar = menubar->make_weak_ptr();
  224. else
  225. m_current_menubar = nullptr;
  226. dbgprintf("[WM] Current menubar is now %p\n", menubar);
  227. Point next_menu_location { menubar_menu_margin() / 2, 3 };
  228. for_each_active_menubar_menu([&] (WSMenu& menu) {
  229. int text_width = font().width(menu.name());
  230. menu.set_rect_in_menubar({ next_menu_location.x() - menubar_menu_margin() / 2, 0, text_width + menubar_menu_margin(), menubar_rect().height() - 1 });
  231. menu.set_text_rect_in_menubar({ next_menu_location, { text_width, font().glyph_height() } });
  232. next_menu_location.move_by(menu.rect_in_menubar().width(), 0);
  233. return true;
  234. });
  235. invalidate();
  236. }
  237. static const char* s_close_button_bitmap_data = {
  238. "## ##"
  239. "### ###"
  240. " ###### "
  241. " #### "
  242. " ## "
  243. " #### "
  244. " ###### "
  245. "### ###"
  246. "## ##"
  247. };
  248. static CharacterBitmap* s_close_button_bitmap;
  249. static const int s_close_button_bitmap_width = 8;
  250. static const int s_close_button_bitmap_height = 9;
  251. void WSWindowManager::paint_window_frame(WSWindow& window)
  252. {
  253. //printf("[WM] paint_window_frame {%p}, rect: %d,%d %dx%d\n", &window, window.rect().x(), window.rect().y(), window.rect().width(), window.rect().height());
  254. if (window.type() == WSWindowType::Menu) {
  255. m_back_painter->draw_rect(window.rect().inflated(2, 2), Color::LightGray);
  256. return;
  257. }
  258. auto titlebar_rect = title_bar_rect(window.rect());
  259. auto titlebar_inner_rect = title_bar_text_rect(window.rect());
  260. auto outer_rect = outer_window_rect(window.rect());
  261. auto border_rect = border_window_rect(window.rect());
  262. auto close_button_rect = close_button_rect_for_window(window.rect());
  263. auto titlebar_title_rect = titlebar_inner_rect;
  264. titlebar_title_rect.set_width(font().glyph_width() * window.title().length());
  265. Rect inner_border_rect {
  266. window.x() - 1,
  267. window.y() - 1,
  268. window.width() + 2,
  269. window.height() + 2
  270. };
  271. Color title_color;
  272. Color border_color;
  273. Color border_color2;
  274. Color middle_border_color;
  275. if (&window == m_drag_window.ptr()) {
  276. border_color = m_dragging_window_border_color;
  277. border_color2 = m_dragging_window_border_color2;
  278. title_color = m_dragging_window_title_color;
  279. middle_border_color = Color::White;
  280. } else if (&window == m_active_window.ptr()) {
  281. border_color = m_active_window_border_color;
  282. border_color2 = m_active_window_border_color2;
  283. title_color = m_active_window_title_color;
  284. middle_border_color = Color::MidGray;
  285. } else {
  286. border_color = m_inactive_window_border_color;
  287. border_color2 = m_inactive_window_border_color2;
  288. title_color = m_inactive_window_title_color;
  289. middle_border_color = Color::MidGray;
  290. }
  291. m_back_painter->fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
  292. for (int i = 2; i <= titlebar_inner_rect.height() - 4; i += 2) {
  293. m_back_painter->draw_line({ titlebar_title_rect.right() + 4, titlebar_inner_rect.y() + i }, { close_button_rect.left() - 3, titlebar_inner_rect.y() + i }, border_color);
  294. }
  295. m_back_painter->draw_rect(border_rect, middle_border_color);
  296. m_back_painter->draw_rect(outer_rect, border_color);
  297. m_back_painter->draw_rect(inner_border_rect, border_color);
  298. m_back_painter->draw_text(titlebar_title_rect, window.title(), TextAlignment::CenterLeft, title_color);
  299. if (!s_close_button_bitmap)
  300. s_close_button_bitmap = CharacterBitmap::create_from_ascii(s_close_button_bitmap_data, s_close_button_bitmap_width, s_close_button_bitmap_height).leak_ref();
  301. m_back_painter->fill_rect_with_gradient(close_button_rect.shrunken(2, 2), Color::LightGray, Color::White);
  302. m_back_painter->draw_rect(close_button_rect, Color::DarkGray, true);
  303. auto x_location = close_button_rect.center();
  304. x_location.move_by(-(s_close_button_bitmap_width / 2), -(s_close_button_bitmap_height / 2));
  305. m_back_painter->draw_bitmap(x_location, *s_close_button_bitmap, Color::Black);
  306. #ifdef DEBUG_WID_IN_TITLE_BAR
  307. Color metadata_color(96, 96, 96);
  308. m_back_painter->draw_text(
  309. titlebar_inner_rect,
  310. String::format("%d:%d", window.pid(), window.window_id()),
  311. TextAlignment::CenterRight,
  312. metadata_color
  313. );
  314. #endif
  315. }
  316. void WSWindowManager::add_window(WSWindow& window)
  317. {
  318. m_windows.set(&window);
  319. m_windows_in_order.append(&window);
  320. if (!active_window())
  321. set_active_window(&window);
  322. }
  323. void WSWindowManager::move_to_front(WSWindow& window)
  324. {
  325. if (m_windows_in_order.tail() != &window)
  326. invalidate(window);
  327. m_windows_in_order.remove(&window);
  328. m_windows_in_order.append(&window);
  329. }
  330. void WSWindowManager::remove_window(WSWindow& window)
  331. {
  332. if (!m_windows.contains(&window))
  333. return;
  334. invalidate(window);
  335. m_windows.remove(&window);
  336. m_windows_in_order.remove(&window);
  337. if (!active_window() && !m_windows.is_empty())
  338. set_active_window(*m_windows.begin());
  339. }
  340. void WSWindowManager::notify_title_changed(WSWindow& window)
  341. {
  342. printf("[WM] WSWindow{%p} title set to '%s'\n", &window, window.title().characters());
  343. invalidate(outer_window_rect(window.rect()));
  344. }
  345. void WSWindowManager::notify_rect_changed(WSWindow& window, const Rect& old_rect, const Rect& new_rect)
  346. {
  347. printf("[WM] WSWindow %p rect changed (%d,%d %dx%d) -> (%d,%d %dx%d)\n", &window, old_rect.x(), old_rect.y(), old_rect.width(), old_rect.height(), new_rect.x(), new_rect.y(), new_rect.width(), new_rect.height());
  348. invalidate(outer_window_rect(old_rect));
  349. invalidate(outer_window_rect(new_rect));
  350. }
  351. void WSWindowManager::handle_menu_mouse_event(WSMenu& menu, WSMouseEvent& event)
  352. {
  353. bool is_hover_with_any_menu_open = event.type() == WSMouseEvent::MouseMove && m_current_menu;
  354. bool is_mousedown_with_left_button = event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left;
  355. bool should_open_menu = &menu != current_menu() && (is_hover_with_any_menu_open || is_mousedown_with_left_button);
  356. if (should_open_menu) {
  357. if (current_menu() == &menu)
  358. return;
  359. close_current_menu();
  360. if (!menu.is_empty()) {
  361. auto& menu_window = menu.ensure_menu_window();
  362. menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() });
  363. menu_window.set_visible(true);
  364. }
  365. m_current_menu = menu.make_weak_ptr();
  366. return;
  367. }
  368. if (event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left) {
  369. close_current_menu();
  370. return;
  371. }
  372. }
  373. void WSWindowManager::close_current_menu()
  374. {
  375. if (m_current_menu && m_current_menu->menu_window())
  376. m_current_menu->menu_window()->set_visible(false);
  377. m_current_menu = nullptr;
  378. }
  379. void WSWindowManager::handle_menubar_mouse_event(WSMouseEvent& event)
  380. {
  381. for_each_active_menubar_menu([&] (WSMenu& menu) {
  382. if (menu.rect_in_menubar().contains(event.position())) {
  383. handle_menu_mouse_event(menu, event);
  384. return false;
  385. }
  386. return true;
  387. });
  388. }
  389. void WSWindowManager::handle_titlebar_mouse_event(WSWindow& window, WSMouseEvent& event)
  390. {
  391. if (event.type() == WSMessage::MouseDown && event.button() == MouseButton::Left) {
  392. #ifdef DRAG_DEBUG
  393. printf("[WM] Begin dragging WSWindow{%p}\n", &window);
  394. #endif
  395. m_drag_window = window.make_weak_ptr();;
  396. m_drag_origin = event.position();
  397. m_drag_window_origin = window.position();
  398. m_drag_start_rect = outer_window_rect(window.rect());
  399. window.set_is_being_dragged(true);
  400. invalidate(window);
  401. return;
  402. }
  403. }
  404. void WSWindowManager::handle_close_button_mouse_event(WSWindow& window, WSMouseEvent& event)
  405. {
  406. if (event.type() == WSMessage::MouseDown && event.button() == MouseButton::Left) {
  407. WSMessage message(WSMessage::WindowCloseRequest);
  408. window.on_message(message);
  409. return;
  410. }
  411. }
  412. void WSWindowManager::process_mouse_event(WSMouseEvent& event)
  413. {
  414. if (event.type() == WSMessage::MouseUp && event.button() == MouseButton::Left) {
  415. if (m_drag_window) {
  416. #ifdef DRAG_DEBUG
  417. printf("[WM] Finish dragging WSWindow{%p}\n", m_drag_window.ptr());
  418. #endif
  419. invalidate(*m_drag_window);
  420. m_drag_window->set_is_being_dragged(false);
  421. m_drag_end_rect = outer_window_rect(m_drag_window->rect());
  422. m_drag_window = nullptr;
  423. return;
  424. }
  425. }
  426. if (event.type() == WSMessage::MouseMove) {
  427. if (m_drag_window) {
  428. auto old_window_rect = m_drag_window->rect();
  429. Point pos = m_drag_window_origin;
  430. #ifdef DRAG_DEBUG
  431. dbgprintf("[WM] Dragging [origin: %d,%d] now: %d,%d\n", m_drag_origin.x(), m_drag_origin.y(), event.x(), event.y());
  432. #endif
  433. pos.move_by(event.x() - m_drag_origin.x(), event.y() - m_drag_origin.y());
  434. m_drag_window->set_position_without_repaint(pos);
  435. invalidate(outer_window_rect(old_window_rect));
  436. invalidate(outer_window_rect(m_drag_window->rect()));
  437. return;
  438. }
  439. }
  440. for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
  441. if (!window->global_cursor_tracking())
  442. continue;
  443. ASSERT(window->is_visible()); // Maybe this should be supported? Idk. Let's catch it and think about it later.
  444. Point position { event.x() - window->rect().x(), event.y() - window->rect().y() };
  445. auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button());
  446. window->on_message(*local_event);
  447. }
  448. if (menubar_rect().contains(event.position())) {
  449. handle_menubar_mouse_event(event);
  450. return;
  451. }
  452. if (m_current_menu) {
  453. bool event_is_inside_current_menu = m_current_menu->menu_window()->rect().contains(event.position());
  454. if (!event_is_inside_current_menu) {
  455. if (m_current_menu->hovered_item())
  456. m_current_menu->clear_hovered_item();
  457. if (event.type() == WSMessage::MouseDown || event.type() == WSMessage::MouseUp)
  458. close_current_menu();
  459. }
  460. }
  461. for_each_visible_window_from_front_to_back([&] (WSWindow& window) {
  462. if (window.type() != WSWindowType::Menu && title_bar_rect(window.rect()).contains(event.position())) {
  463. if (event.type() == WSMessage::MouseDown) {
  464. move_to_front(window);
  465. set_active_window(&window);
  466. }
  467. if (close_button_rect_for_window(window.rect()).contains(event.position())) {
  468. handle_close_button_mouse_event(window, event);
  469. return IterationDecision::Abort;
  470. }
  471. handle_titlebar_mouse_event(window, event);
  472. return IterationDecision::Abort;
  473. }
  474. if (window.rect().contains(event.position())) {
  475. if (window.type() != WSWindowType::Menu && event.type() == WSMessage::MouseDown) {
  476. move_to_front(window);
  477. set_active_window(&window);
  478. }
  479. // FIXME: Should we just alter the coordinates of the existing MouseEvent and pass it through?
  480. Point position { event.x() - window.rect().x(), event.y() - window.rect().y() };
  481. auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button());
  482. window.on_message(*local_event);
  483. return IterationDecision::Abort;
  484. }
  485. return IterationDecision::Continue;
  486. });
  487. }
  488. template<typename Callback>
  489. IterationDecision WSWindowManager::for_each_visible_window_of_type_from_back_to_front(WSWindowType type, Callback callback)
  490. {
  491. for (auto* window = m_windows_in_order.head(); window; window = window->next()) {
  492. if (!window->is_visible())
  493. continue;
  494. if (window->type() != type)
  495. continue;
  496. if (callback(*window) == IterationDecision::Abort)
  497. return IterationDecision::Abort;
  498. }
  499. return IterationDecision::Continue;
  500. }
  501. template<typename Callback>
  502. IterationDecision WSWindowManager::for_each_visible_window_from_back_to_front(Callback callback)
  503. {
  504. if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Normal, callback) == IterationDecision::Abort)
  505. return IterationDecision::Abort;
  506. return for_each_visible_window_of_type_from_back_to_front(WSWindowType::Menu, callback);
  507. }
  508. template<typename Callback>
  509. IterationDecision WSWindowManager::for_each_visible_window_of_type_from_front_to_back(WSWindowType type, Callback callback)
  510. {
  511. for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
  512. if (!window->is_visible())
  513. continue;
  514. if (window->type() != type)
  515. continue;
  516. if (callback(*window) == IterationDecision::Abort)
  517. return IterationDecision::Abort;
  518. }
  519. return IterationDecision::Continue;
  520. }
  521. template<typename Callback>
  522. IterationDecision WSWindowManager::for_each_visible_window_from_front_to_back(Callback callback)
  523. {
  524. if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Menu, callback) == IterationDecision::Abort)
  525. return IterationDecision::Abort;
  526. return for_each_visible_window_of_type_from_front_to_back(WSWindowType::Normal, callback);
  527. }
  528. void WSWindowManager::compose()
  529. {
  530. auto dirty_rects = move(m_dirty_rects);
  531. auto cursor_location = m_screen.cursor_location();
  532. dirty_rects.append(m_last_cursor_rect);
  533. dirty_rects.append({ cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() });
  534. #ifdef DEBUG_COUNTERS
  535. dbgprintf("[WM] compose #%u (%u rects)\n", ++m_compose_count, dirty_rects.size());
  536. #endif
  537. auto any_window_contains_rect = [this] (const Rect& r) {
  538. for (auto* window = m_windows_in_order.head(); window; window = window->next()) {
  539. if (!window->is_visible())
  540. continue;
  541. if (outer_window_rect(window->rect()).contains(r))
  542. return true;
  543. }
  544. return false;
  545. };
  546. auto any_dirty_rect_intersects_window = [&dirty_rects] (const WSWindow& window) {
  547. auto window_rect = outer_window_rect(window.rect());
  548. for (auto& dirty_rect : dirty_rects) {
  549. if (dirty_rect.intersects(window_rect))
  550. return true;
  551. }
  552. return false;
  553. };
  554. for (auto& dirty_rect : dirty_rects) {
  555. if (any_window_contains_rect(dirty_rect)) {
  556. continue;
  557. }
  558. if (!m_wallpaper)
  559. m_back_painter->fill_rect(dirty_rect, m_background_color);
  560. else
  561. m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
  562. }
  563. for_each_visible_window_from_back_to_front([&] (WSWindow& window) {
  564. RetainPtr<GraphicsBitmap> backing = window.backing();
  565. if (!backing)
  566. return IterationDecision::Continue;
  567. if (!any_dirty_rect_intersects_window(window))
  568. return IterationDecision::Continue;
  569. for (auto& dirty_rect : dirty_rects) {
  570. m_back_painter->set_clip_rect(dirty_rect);
  571. paint_window_frame(window);
  572. Rect dirty_rect_in_window_coordinates = Rect::intersection(dirty_rect, window.rect());
  573. if (dirty_rect_in_window_coordinates.is_empty())
  574. continue;
  575. dirty_rect_in_window_coordinates.set_x(dirty_rect_in_window_coordinates.x() - window.x());
  576. dirty_rect_in_window_coordinates.set_y(dirty_rect_in_window_coordinates.y() - window.y());
  577. auto dst = window.position();
  578. dst.move_by(dirty_rect_in_window_coordinates.location());
  579. m_back_painter->blit(dst, *backing, dirty_rect_in_window_coordinates);
  580. m_back_painter->clear_clip_rect();
  581. }
  582. m_back_painter->clear_clip_rect();
  583. return IterationDecision::Continue;
  584. });
  585. draw_menubar();
  586. draw_cursor();
  587. if (m_flash_flush) {
  588. for (auto& rect : dirty_rects)
  589. m_front_painter->fill_rect(rect, Color::Yellow);
  590. }
  591. flip_buffers();
  592. for (auto& r : dirty_rects)
  593. flush(r);
  594. }
  595. void WSWindowManager::invalidate_cursor()
  596. {
  597. auto cursor_location = m_screen.cursor_location();
  598. Rect cursor_rect { cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() };
  599. invalidate(cursor_rect);
  600. }
  601. Rect WSWindowManager::menubar_rect() const
  602. {
  603. return { 0, 0, m_screen_rect.width(), 16 };
  604. }
  605. void WSWindowManager::draw_menubar()
  606. {
  607. m_back_painter->fill_rect(menubar_rect(), Color::LightGray);
  608. m_back_painter->draw_line({ 0, menubar_rect().bottom() }, { menubar_rect().right(), menubar_rect().bottom() }, Color::White);
  609. for_each_active_menubar_menu([&] (WSMenu& menu) {
  610. Color text_color = Color::Black;
  611. if (&menu == current_menu()) {
  612. m_back_painter->fill_rect(menu.rect_in_menubar(), menu_selection_color());
  613. text_color = Color::White;
  614. }
  615. m_back_painter->draw_text(menu.text_rect_in_menubar(), menu.name(), TextAlignment::CenterLeft, text_color);
  616. return true;
  617. });
  618. time_t now = time(nullptr);
  619. auto* tm = localtime(&now);
  620. auto time_text = String::format("%4u-%02u-%02u %02u:%02u:%02u\n",
  621. tm->tm_year + 1900,
  622. tm->tm_mon + 1,
  623. tm->tm_mday,
  624. tm->tm_hour,
  625. tm->tm_min,
  626. tm->tm_sec);
  627. auto time_rect = menubar_rect().translated(-(menubar_menu_margin() / 2), 0);
  628. m_back_painter->draw_text(time_rect, time_text, TextAlignment::CenterRight, Color::Black);
  629. }
  630. void WSWindowManager::draw_cursor()
  631. {
  632. auto cursor_location = m_screen.cursor_location();
  633. Rect cursor_rect { cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() };
  634. Color inner_color = Color::White;
  635. Color outer_color = Color::Black;
  636. if (m_screen.left_mouse_button_pressed())
  637. swap(inner_color, outer_color);
  638. m_back_painter->draw_bitmap(cursor_location, *m_cursor_bitmap_inner, inner_color);
  639. m_back_painter->draw_bitmap(cursor_location, *m_cursor_bitmap_outer, outer_color);
  640. m_last_cursor_rect = cursor_rect;
  641. }
  642. void WSWindowManager::on_message(WSMessage& message)
  643. {
  644. if (message.is_mouse_event())
  645. return process_mouse_event(static_cast<WSMouseEvent&>(message));
  646. if (message.is_key_event()) {
  647. // FIXME: This is a good place to hook key events globally. :)
  648. if (m_active_window)
  649. return m_active_window->on_message(message);
  650. return;
  651. }
  652. if (message.type() == WSMessage::WM_DeferredCompose) {
  653. m_pending_compose_event = false;
  654. compose();
  655. return;
  656. }
  657. }
  658. void WSWindowManager::set_active_window(WSWindow* window)
  659. {
  660. if (window->type() == WSWindowType::Menu) {
  661. dbgprintf("WSWindowManager: Attempted to make a menu window active.\n");
  662. return;
  663. }
  664. if (window == m_active_window.ptr())
  665. return;
  666. if (auto* previously_active_window = m_active_window.ptr()) {
  667. WSMessageLoop::the().post_message(previously_active_window, make<WSMessage>(WSMessage::WindowDeactivated));
  668. invalidate(*previously_active_window);
  669. }
  670. m_active_window = window->make_weak_ptr();
  671. if (m_active_window) {
  672. WSMessageLoop::the().post_message(m_active_window.ptr(), make<WSMessage>(WSMessage::WindowActivated));
  673. invalidate(*m_active_window);
  674. int client_id = window->client_id();
  675. auto* client = WSClientConnection::from_client_id(client_id);
  676. if (!client) {
  677. dbgprintf("WSWindow{%p} (type=%u) has no client! (id=%d)\n", window, window->type(), client_id);
  678. ASSERT_NOT_REACHED();
  679. }
  680. set_current_menubar(client->app_menubar());
  681. }
  682. }
  683. void WSWindowManager::invalidate()
  684. {
  685. m_dirty_rects.clear_with_capacity();
  686. invalidate(m_screen_rect);
  687. }
  688. void WSWindowManager::invalidate(const Rect& a_rect)
  689. {
  690. auto rect = Rect::intersection(a_rect, m_screen_rect);
  691. if (rect.is_empty())
  692. return;
  693. for (auto& r : m_dirty_rects) {
  694. if (r.contains(rect))
  695. return;
  696. if (r.intersects(rect)) {
  697. // Unite with the existing dirty rect.
  698. // FIXME: It would be much nicer to compute the exact rects needing repaint.
  699. r = r.united(rect);
  700. return;
  701. }
  702. }
  703. m_dirty_rects.append(rect);
  704. if (!m_pending_compose_event) {
  705. WSMessageLoop::the().post_message(this, make<WSMessage>(WSMessage::WM_DeferredCompose));
  706. m_pending_compose_event = true;
  707. }
  708. }
  709. void WSWindowManager::invalidate(const WSWindow& window)
  710. {
  711. invalidate(outer_window_rect(window.rect()));
  712. }
  713. void WSWindowManager::invalidate(const WSWindow& window, const Rect& rect)
  714. {
  715. if (rect.is_empty()) {
  716. invalidate(window);
  717. return;
  718. }
  719. auto outer_rect = outer_window_rect(window.rect());
  720. auto inner_rect = rect;
  721. inner_rect.move_by(window.position());
  722. // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
  723. inner_rect.intersect(outer_rect);
  724. invalidate(inner_rect);
  725. }
  726. void WSWindowManager::flush(const Rect& a_rect)
  727. {
  728. auto rect = Rect::intersection(a_rect, m_screen_rect);
  729. #ifdef DEBUG_COUNTERS
  730. dbgprintf("[WM] flush #%u (%d,%d %dx%d)\n", ++m_flush_count, rect.x(), rect.y(), rect.width(), rect.height());
  731. #endif
  732. RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
  733. RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
  734. size_t pitch = m_back_bitmap->pitch();
  735. for (int y = 0; y < rect.height(); ++y) {
  736. fast_dword_copy(back_ptr, front_ptr, rect.width());
  737. front_ptr = (RGBA32*)((byte*)front_ptr + pitch);
  738. back_ptr = (RGBA32*)((byte*)back_ptr + pitch);
  739. }
  740. }
  741. void WSWindowManager::close_menu(WSMenu& menu)
  742. {
  743. if (current_menu() == &menu)
  744. close_current_menu();
  745. }
  746. void WSWindowManager::close_menubar(WSMenuBar& menubar)
  747. {
  748. if (current_menubar() == &menubar)
  749. set_current_menubar(nullptr);
  750. }
  751. int WSWindowManager::active_client_id() const
  752. {
  753. if (m_active_window)
  754. return m_active_window->client_id();
  755. return 0;
  756. }
  757. void WSWindowManager::notify_client_changed_app_menubar(WSClientConnection& client)
  758. {
  759. if (active_client_id() == client.client_id())
  760. set_current_menubar(client.app_menubar());
  761. invalidate();
  762. }