Window.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "Window.h"
  27. #include "AppletManager.h"
  28. #include "ClientConnection.h"
  29. #include "Compositor.h"
  30. #include "Event.h"
  31. #include "EventLoop.h"
  32. #include "Screen.h"
  33. #include "WindowManager.h"
  34. #include <AK/Badge.h>
  35. #include <WindowServer/WindowClientEndpoint.h>
  36. #include <ctype.h>
  37. namespace WindowServer {
  38. const static Gfx::IntSize s_default_normal_minimum_size = { 50, 50 };
  39. static String default_window_icon_path()
  40. {
  41. return "/res/icons/16x16/window.png";
  42. }
  43. static Gfx::Bitmap& default_window_icon()
  44. {
  45. static Gfx::Bitmap* s_icon;
  46. if (!s_icon)
  47. s_icon = Gfx::Bitmap::load_from_file(default_window_icon_path()).leak_ref();
  48. return *s_icon;
  49. }
  50. static Gfx::Bitmap& minimize_icon()
  51. {
  52. static Gfx::Bitmap* s_icon;
  53. if (!s_icon)
  54. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png").leak_ref();
  55. return *s_icon;
  56. }
  57. static Gfx::Bitmap& maximize_icon()
  58. {
  59. static Gfx::Bitmap* s_icon;
  60. if (!s_icon)
  61. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png").leak_ref();
  62. return *s_icon;
  63. }
  64. static Gfx::Bitmap& restore_icon()
  65. {
  66. static Gfx::Bitmap* s_icon;
  67. if (!s_icon)
  68. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png").leak_ref();
  69. return *s_icon;
  70. }
  71. static Gfx::Bitmap& close_icon()
  72. {
  73. static Gfx::Bitmap* s_icon;
  74. if (!s_icon)
  75. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png").leak_ref();
  76. return *s_icon;
  77. }
  78. Window::Window(Core::Object& parent, WindowType type)
  79. : Core::Object(&parent)
  80. , m_type(type)
  81. , m_icon(default_window_icon())
  82. , m_frame(*this)
  83. {
  84. // Set default minimum size for Normal windows
  85. if (m_type == WindowType::Normal)
  86. m_minimum_size = s_default_normal_minimum_size;
  87. WindowManager::the().add_window(*this);
  88. }
  89. Window::Window(ClientConnection& client, WindowType window_type, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window)
  90. : Core::Object(&client)
  91. , m_client(&client)
  92. , m_type(window_type)
  93. , m_modal(modal)
  94. , m_minimizable(minimizable)
  95. , m_frameless(frameless)
  96. , m_resizable(resizable)
  97. , m_fullscreen(fullscreen)
  98. , m_accessory(accessory)
  99. , m_window_id(window_id)
  100. , m_client_id(client.client_id())
  101. , m_icon(default_window_icon())
  102. , m_frame(*this)
  103. {
  104. // FIXME: This should not be hard-coded here.
  105. if (m_type == WindowType::Taskbar) {
  106. m_wm_event_mask = WMEventMask::WindowStateChanges | WMEventMask::WindowRemovals | WMEventMask::WindowIconChanges;
  107. m_listens_to_wm_events = true;
  108. }
  109. // Set default minimum size for Normal windows
  110. if (m_type == WindowType::Normal)
  111. m_minimum_size = s_default_normal_minimum_size;
  112. if (parent_window)
  113. set_parent_window(*parent_window);
  114. WindowManager::the().add_window(*this);
  115. }
  116. Window::~Window()
  117. {
  118. // Detach from client at the start of teardown since we don't want
  119. // to confuse things by trying to send messages to it.
  120. m_client = nullptr;
  121. WindowManager::the().remove_window(*this);
  122. }
  123. void Window::destroy()
  124. {
  125. m_destroyed = true;
  126. set_visible(false);
  127. }
  128. void Window::set_title(const String& title)
  129. {
  130. if (m_title == title)
  131. return;
  132. m_title = title;
  133. frame().invalidate_title_bar();
  134. WindowManager::the().notify_title_changed(*this);
  135. }
  136. void Window::set_rect(const Gfx::IntRect& rect)
  137. {
  138. if (m_rect == rect)
  139. return;
  140. auto old_rect = m_rect;
  141. m_rect = rect;
  142. if (rect.is_empty()) {
  143. m_backing_store = nullptr;
  144. } else if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) {
  145. m_backing_store = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, m_rect.size());
  146. }
  147. invalidate(true, old_rect.size() != rect.size());
  148. m_frame.notify_window_rect_changed(old_rect, rect); // recomputes occlusions
  149. }
  150. void Window::set_rect_without_repaint(const Gfx::IntRect& rect)
  151. {
  152. VERIFY(!rect.is_empty());
  153. if (m_rect == rect)
  154. return;
  155. auto old_rect = m_rect;
  156. m_rect = rect;
  157. if (old_rect.size() == m_rect.size()) {
  158. auto delta = m_rect.location() - old_rect.location();
  159. for (auto& child_window : m_child_windows) {
  160. if (child_window)
  161. child_window->move_by(delta);
  162. }
  163. }
  164. invalidate(true, old_rect.size() != rect.size());
  165. m_frame.notify_window_rect_changed(old_rect, rect); // recomputes occlusions
  166. }
  167. bool Window::apply_minimum_size(Gfx::IntRect& rect)
  168. {
  169. int new_width = max(m_minimum_size.width(), rect.width());
  170. int new_height = max(m_minimum_size.height(), rect.height());
  171. bool did_size_clamp = new_width != rect.width() || new_height != rect.height();
  172. rect.set_width(new_width);
  173. rect.set_height(new_height);
  174. return did_size_clamp;
  175. }
  176. void Window::nudge_into_desktop(bool force_titlebar_visible)
  177. {
  178. Gfx::IntRect arena = WindowManager::the().arena_rect_for_type(type());
  179. auto min_visible = 1;
  180. if (type() == WindowType::Normal)
  181. min_visible = 30;
  182. // Push the frame around such that at least `min_visible` pixels of the *frame* are in the desktop rect.
  183. auto old_frame_rect = frame().rect();
  184. Gfx::IntRect new_frame_rect = {
  185. clamp(old_frame_rect.x(), arena.left() + min_visible - width(), arena.right() - min_visible),
  186. clamp(old_frame_rect.y(), arena.top() + min_visible - height(), arena.bottom() - min_visible),
  187. old_frame_rect.width(),
  188. old_frame_rect.height(),
  189. };
  190. // Make sure that at least half of the titlebar is visible.
  191. auto min_frame_y = arena.top() - (y() - old_frame_rect.y()) / 2;
  192. if (force_titlebar_visible && new_frame_rect.y() < min_frame_y) {
  193. new_frame_rect.set_y(min_frame_y);
  194. }
  195. // Deduce new window rect:
  196. Gfx::IntRect new_window_rect = {
  197. x() + new_frame_rect.x() - old_frame_rect.x(),
  198. y() + new_frame_rect.y() - old_frame_rect.y(),
  199. width(),
  200. height(),
  201. };
  202. set_rect(new_window_rect);
  203. }
  204. void Window::set_minimum_size(const Gfx::IntSize& size)
  205. {
  206. VERIFY(!size.is_empty());
  207. if (m_minimum_size == size)
  208. return;
  209. // Disallow setting minimum zero widths or heights.
  210. if (size.width() == 0 || size.height() == 0)
  211. return;
  212. m_minimum_size = size;
  213. }
  214. void Window::handle_mouse_event(const MouseEvent& event)
  215. {
  216. set_automatic_cursor_tracking_enabled(event.buttons() != 0);
  217. switch (event.type()) {
  218. case Event::MouseMove:
  219. m_client->post_message(Messages::WindowClient::MouseMove(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta(), event.is_drag(), event.mime_types()));
  220. break;
  221. case Event::MouseDown:
  222. m_client->post_message(Messages::WindowClient::MouseDown(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
  223. break;
  224. case Event::MouseDoubleClick:
  225. m_client->post_message(Messages::WindowClient::MouseDoubleClick(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
  226. break;
  227. case Event::MouseUp:
  228. m_client->post_message(Messages::WindowClient::MouseUp(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
  229. break;
  230. case Event::MouseWheel:
  231. m_client->post_message(Messages::WindowClient::MouseWheel(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
  232. break;
  233. default:
  234. VERIFY_NOT_REACHED();
  235. }
  236. }
  237. void Window::update_menu_item_text(PopupMenuItem item)
  238. {
  239. if (m_window_menu) {
  240. m_window_menu->item((int)item).set_text(item == PopupMenuItem::Minimize ? (m_minimized ? "Unminimize" : "Minimize") : (m_maximized ? "Restore" : "Maximize"));
  241. m_window_menu->redraw();
  242. }
  243. }
  244. void Window::update_menu_item_enabled(PopupMenuItem item)
  245. {
  246. if (m_window_menu) {
  247. m_window_menu->item((int)item).set_enabled(item == PopupMenuItem::Minimize ? m_minimizable : m_resizable);
  248. m_window_menu->redraw();
  249. }
  250. }
  251. void Window::set_minimized(bool minimized)
  252. {
  253. if (m_minimized == minimized)
  254. return;
  255. if (minimized && !m_minimizable)
  256. return;
  257. m_minimized = minimized;
  258. update_menu_item_text(PopupMenuItem::Minimize);
  259. Compositor::the().invalidate_occlusions();
  260. Compositor::the().invalidate_screen(frame().render_rect());
  261. if (!blocking_modal_window())
  262. start_minimize_animation();
  263. if (!minimized)
  264. request_update({ {}, size() });
  265. WindowManager::the().notify_minimization_state_changed(*this);
  266. }
  267. void Window::set_minimizable(bool minimizable)
  268. {
  269. if (m_minimizable == minimizable)
  270. return;
  271. m_minimizable = minimizable;
  272. update_menu_item_enabled(PopupMenuItem::Minimize);
  273. // TODO: Hide/show (or alternatively change enabled state of) window minimize button dynamically depending on value of m_minimizable
  274. }
  275. void Window::set_taskbar_rect(const Gfx::IntRect& rect)
  276. {
  277. m_taskbar_rect = rect;
  278. m_have_taskbar_rect = !m_taskbar_rect.is_empty();
  279. }
  280. void Window::start_minimize_animation()
  281. {
  282. if (!m_have_taskbar_rect) {
  283. // If this is a modal window, it may not have its own taskbar
  284. // button, so there is no rectangle. In that case, walk the
  285. // modal stack until we find a window that may have one
  286. WindowManager::the().for_each_window_in_modal_stack(*this, [&](Window& w, bool) {
  287. if (w.has_taskbar_rect()) {
  288. // We purposely do NOT set m_have_taskbar_rect to true here
  289. // because we want to only copy the rectangle from the
  290. // window that has it, but since this window wouldn't receive
  291. // any updates down the road we want to query it again
  292. // next time we want to start the animation
  293. m_taskbar_rect = w.taskbar_rect();
  294. VERIFY(!m_have_taskbar_rect); // should remain unset!
  295. return IterationDecision::Break;
  296. };
  297. return IterationDecision::Continue;
  298. });
  299. }
  300. m_minimize_animation_step = 0;
  301. }
  302. void Window::set_opacity(float opacity)
  303. {
  304. if (m_opacity == opacity)
  305. return;
  306. bool was_opaque = is_opaque();
  307. m_opacity = opacity;
  308. if (was_opaque != is_opaque())
  309. Compositor::the().invalidate_occlusions();
  310. invalidate(false);
  311. WindowManager::the().notify_opacity_changed(*this);
  312. }
  313. void Window::set_has_alpha_channel(bool value)
  314. {
  315. if (m_has_alpha_channel == value)
  316. return;
  317. m_has_alpha_channel = value;
  318. Compositor::the().invalidate_occlusions();
  319. }
  320. void Window::set_occluded(bool occluded)
  321. {
  322. if (m_occluded == occluded)
  323. return;
  324. m_occluded = occluded;
  325. WindowManager::the().notify_occlusion_state_changed(*this);
  326. }
  327. void Window::set_maximized(bool maximized, Optional<Gfx::IntPoint> fixed_point)
  328. {
  329. if (m_maximized == maximized)
  330. return;
  331. if (maximized && (!is_resizable() || resize_aspect_ratio().has_value()))
  332. return;
  333. m_tiled = WindowTileType::None;
  334. m_maximized = maximized;
  335. update_menu_item_text(PopupMenuItem::Maximize);
  336. if (maximized) {
  337. m_unmaximized_rect = m_rect;
  338. set_rect(WindowManager::the().maximized_window_rect(*this));
  339. } else {
  340. if (fixed_point.has_value()) {
  341. auto new_rect = Gfx::IntRect(m_rect);
  342. new_rect.set_size_around(m_unmaximized_rect.size(), fixed_point.value());
  343. set_rect(new_rect);
  344. } else {
  345. set_rect(m_unmaximized_rect);
  346. }
  347. }
  348. m_frame.did_set_maximized({}, maximized);
  349. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  350. set_default_positioned(false);
  351. }
  352. void Window::set_vertically_maximized()
  353. {
  354. if (m_maximized)
  355. return;
  356. if (!is_resizable() || resize_aspect_ratio().has_value())
  357. return;
  358. auto max_rect = WindowManager::the().maximized_window_rect(*this);
  359. auto new_rect = Gfx::IntRect(
  360. Gfx::IntPoint(rect().x(), max_rect.y()),
  361. Gfx::IntSize(rect().width(), max_rect.height()));
  362. set_rect(new_rect);
  363. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(new_rect));
  364. }
  365. void Window::set_resizable(bool resizable)
  366. {
  367. if (m_resizable == resizable)
  368. return;
  369. m_resizable = resizable;
  370. update_menu_item_enabled(PopupMenuItem::Maximize);
  371. // TODO: Hide/show (or alternatively change enabled state of) window maximize button dynamically depending on value of is_resizable()
  372. }
  373. void Window::event(Core::Event& event)
  374. {
  375. if (!m_client) {
  376. VERIFY(parent());
  377. event.ignore();
  378. return;
  379. }
  380. if (blocking_modal_window()) {
  381. // We still want to handle the WindowDeactivated event below when a new modal is
  382. // created to notify its parent window, despite it being "blocked by modal window".
  383. if (event.type() != Event::WindowDeactivated)
  384. return;
  385. }
  386. if (static_cast<Event&>(event).is_mouse_event())
  387. return handle_mouse_event(static_cast<const MouseEvent&>(event));
  388. switch (event.type()) {
  389. case Event::WindowEntered:
  390. m_client->post_message(Messages::WindowClient::WindowEntered(m_window_id));
  391. break;
  392. case Event::WindowLeft:
  393. m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id));
  394. break;
  395. case Event::KeyDown:
  396. handle_keydown_event(static_cast<const KeyEvent&>(event));
  397. break;
  398. case Event::KeyUp:
  399. m_client->post_message(
  400. Messages::WindowClient::KeyUp(m_window_id,
  401. (u32) static_cast<const KeyEvent&>(event).code_point(),
  402. (u32) static_cast<const KeyEvent&>(event).key(),
  403. static_cast<const KeyEvent&>(event).modifiers(),
  404. (u32) static_cast<const KeyEvent&>(event).scancode()));
  405. break;
  406. case Event::WindowActivated:
  407. m_client->post_message(Messages::WindowClient::WindowActivated(m_window_id));
  408. break;
  409. case Event::WindowDeactivated:
  410. m_client->post_message(Messages::WindowClient::WindowDeactivated(m_window_id));
  411. break;
  412. case Event::WindowInputEntered:
  413. m_client->post_message(Messages::WindowClient::WindowInputEntered(m_window_id));
  414. break;
  415. case Event::WindowInputLeft:
  416. m_client->post_message(Messages::WindowClient::WindowInputLeft(m_window_id));
  417. break;
  418. case Event::WindowCloseRequest:
  419. m_client->post_message(Messages::WindowClient::WindowCloseRequest(m_window_id));
  420. break;
  421. case Event::WindowResized:
  422. m_client->post_message(Messages::WindowClient::WindowResized(m_window_id, static_cast<const ResizeEvent&>(event).rect()));
  423. break;
  424. default:
  425. break;
  426. }
  427. }
  428. void Window::handle_keydown_event(const KeyEvent& event)
  429. {
  430. if (event.modifiers() == Mod_Alt && event.code_point() && menubar()) {
  431. Menu* menu_to_open = nullptr;
  432. menubar()->for_each_menu([&](Menu& menu) {
  433. if (tolower(menu.alt_shortcut_character()) == tolower(event.code_point())) {
  434. menu_to_open = &menu;
  435. return IterationDecision::Break;
  436. }
  437. return IterationDecision::Continue;
  438. });
  439. if (menu_to_open) {
  440. frame().open_menubar_menu(*menu_to_open);
  441. if (!menu_to_open->is_empty())
  442. menu_to_open->set_hovered_index(0);
  443. return;
  444. }
  445. }
  446. m_client->post_message(Messages::WindowClient::KeyDown(m_window_id, (u32)event.code_point(), (u32)event.key(), event.modifiers(), (u32)event.scancode()));
  447. }
  448. void Window::set_global_cursor_tracking_enabled(bool enabled)
  449. {
  450. m_global_cursor_tracking_enabled = enabled;
  451. }
  452. void Window::set_visible(bool b)
  453. {
  454. if (m_visible == b)
  455. return;
  456. m_visible = b;
  457. Compositor::the().invalidate_occlusions();
  458. if (m_visible)
  459. invalidate(true);
  460. else
  461. Compositor::the().invalidate_screen(frame().render_rect());
  462. }
  463. void Window::set_frameless(bool frameless)
  464. {
  465. if (m_frameless == frameless)
  466. return;
  467. auto render_rect_before = frame().render_rect();
  468. m_frameless = frameless;
  469. if (m_visible) {
  470. Compositor::the().invalidate_occlusions();
  471. invalidate(true, true);
  472. Compositor::the().invalidate_screen(frameless ? render_rect_before : frame().render_rect());
  473. }
  474. }
  475. void Window::invalidate(bool invalidate_frame, bool re_render_frame)
  476. {
  477. m_invalidated = true;
  478. m_invalidated_all = true;
  479. if (invalidate_frame && !m_invalidated_frame) {
  480. m_invalidated_frame = true;
  481. }
  482. if (re_render_frame)
  483. frame().set_dirty(true);
  484. m_dirty_rects.clear();
  485. Compositor::the().invalidate_window();
  486. }
  487. void Window::invalidate(const Gfx::IntRect& rect, bool with_frame)
  488. {
  489. if (type() == WindowType::Applet) {
  490. AppletManager::the().invalidate_applet(*this, rect);
  491. return;
  492. }
  493. if (invalidate_no_notify(rect, with_frame))
  494. Compositor::the().invalidate_window();
  495. }
  496. bool Window::invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame)
  497. {
  498. if (rect.is_empty())
  499. return false;
  500. if (m_invalidated_all) {
  501. if (with_frame)
  502. m_invalidated_frame |= true;
  503. return false;
  504. }
  505. auto outer_rect = frame().render_rect();
  506. auto inner_rect = rect;
  507. inner_rect.move_by(position());
  508. // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
  509. inner_rect.intersect(outer_rect);
  510. if (inner_rect.is_empty())
  511. return false;
  512. m_invalidated = true;
  513. if (with_frame)
  514. m_invalidated_frame |= true;
  515. m_dirty_rects.add(inner_rect.translated(-outer_rect.location()));
  516. return true;
  517. }
  518. void Window::refresh_client_size()
  519. {
  520. client()->post_message(Messages::WindowClient::WindowResized(m_window_id, m_rect));
  521. }
  522. void Window::prepare_dirty_rects()
  523. {
  524. if (m_invalidated_all) {
  525. if (m_invalidated_frame)
  526. m_dirty_rects = frame().render_rect();
  527. else
  528. m_dirty_rects = rect();
  529. } else {
  530. m_dirty_rects.move_by(frame().render_rect().location());
  531. if (m_invalidated_frame) {
  532. if (m_invalidated) {
  533. m_dirty_rects.add(frame().render_rect());
  534. } else {
  535. for (auto& rects : frame().render_rect().shatter(rect()))
  536. m_dirty_rects.add(rects);
  537. }
  538. }
  539. }
  540. }
  541. void Window::clear_dirty_rects()
  542. {
  543. m_invalidated_all = false;
  544. m_invalidated_frame = false;
  545. m_invalidated = false;
  546. m_dirty_rects.clear_with_capacity();
  547. }
  548. bool Window::is_active() const
  549. {
  550. return WindowManager::the().active_window() == this;
  551. }
  552. Window* Window::blocking_modal_window()
  553. {
  554. // A window is blocked if any immediate child, or any child further
  555. // down the chain is modal
  556. for (auto& window : m_child_windows) {
  557. if (window && !window->is_destroyed()) {
  558. if (window->is_modal())
  559. return window;
  560. if (auto* blocking_window = window->blocking_modal_window())
  561. return blocking_window;
  562. }
  563. }
  564. return nullptr;
  565. }
  566. void Window::set_default_icon()
  567. {
  568. m_icon = default_window_icon();
  569. }
  570. void Window::request_update(const Gfx::IntRect& rect, bool ignore_occlusion)
  571. {
  572. if (rect.is_empty())
  573. return;
  574. if (m_pending_paint_rects.is_empty()) {
  575. deferred_invoke([this, ignore_occlusion](auto&) {
  576. client()->post_paint_message(*this, ignore_occlusion);
  577. });
  578. }
  579. m_pending_paint_rects.add(rect);
  580. }
  581. void Window::ensure_window_menu()
  582. {
  583. if (!m_window_menu) {
  584. m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)");
  585. m_window_menu->set_window_menu_of(*this);
  586. auto minimize_item = make<MenuItem>(*m_window_menu, 1, m_minimized ? "&Unminimize" : "Mi&nimize");
  587. m_window_menu_minimize_item = minimize_item.ptr();
  588. m_window_menu->add_item(move(minimize_item));
  589. auto maximize_item = make<MenuItem>(*m_window_menu, 2, m_maximized ? "&Restore" : "Ma&ximize");
  590. m_window_menu_maximize_item = maximize_item.ptr();
  591. m_window_menu->add_item(move(maximize_item));
  592. m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator));
  593. auto menubar_visibility_item = make<MenuItem>(*m_window_menu, 4, "Menu &Bar");
  594. m_window_menu_menubar_visibility_item = menubar_visibility_item.ptr();
  595. menubar_visibility_item->set_checkable(true);
  596. m_window_menu->add_item(move(menubar_visibility_item));
  597. auto close_item = make<MenuItem>(*m_window_menu, 3, "&Close");
  598. m_window_menu_close_item = close_item.ptr();
  599. m_window_menu_close_item->set_icon(&close_icon());
  600. m_window_menu_close_item->set_default(true);
  601. m_window_menu->add_item(move(close_item));
  602. m_window_menu->item((int)PopupMenuItem::Minimize).set_enabled(m_minimizable);
  603. m_window_menu->item((int)PopupMenuItem::Maximize).set_enabled(m_resizable);
  604. m_window_menu->on_item_activation = [&](auto& item) {
  605. switch (item.identifier()) {
  606. case 1:
  607. WindowManager::the().minimize_windows(*this, !m_minimized);
  608. if (!m_minimized)
  609. WindowManager::the().move_to_front_and_make_active(*this);
  610. break;
  611. case 2:
  612. WindowManager::the().maximize_windows(*this, !m_maximized);
  613. WindowManager::the().move_to_front_and_make_active(*this);
  614. break;
  615. case 3:
  616. request_close();
  617. break;
  618. case 4:
  619. frame().invalidate();
  620. item.set_checked(!item.is_checked());
  621. m_should_show_menubar = item.is_checked();
  622. frame().invalidate();
  623. Compositor::the().invalidate_occlusions();
  624. Compositor::the().invalidate_screen();
  625. break;
  626. }
  627. };
  628. }
  629. }
  630. void Window::popup_window_menu(const Gfx::IntPoint& position, WindowMenuDefaultAction default_action)
  631. {
  632. ensure_window_menu();
  633. if (default_action == WindowMenuDefaultAction::BasedOnWindowState) {
  634. // When clicked on the task bar, determine the default action
  635. if (!is_active() && !is_minimized())
  636. default_action = WindowMenuDefaultAction::None;
  637. else if (is_minimized())
  638. default_action = WindowMenuDefaultAction::Unminimize;
  639. else
  640. default_action = WindowMenuDefaultAction::Minimize;
  641. }
  642. m_window_menu_minimize_item->set_default(default_action == WindowMenuDefaultAction::Minimize || default_action == WindowMenuDefaultAction::Unminimize);
  643. m_window_menu_minimize_item->set_icon(m_minimized ? nullptr : &minimize_icon());
  644. m_window_menu_maximize_item->set_default(default_action == WindowMenuDefaultAction::Maximize || default_action == WindowMenuDefaultAction::Restore);
  645. m_window_menu_maximize_item->set_icon(m_maximized ? &restore_icon() : &maximize_icon());
  646. m_window_menu_close_item->set_default(default_action == WindowMenuDefaultAction::Close);
  647. m_window_menu_menubar_visibility_item->set_enabled(menubar());
  648. m_window_menu_menubar_visibility_item->set_checked(menubar() && m_should_show_menubar);
  649. m_window_menu->popup(position);
  650. }
  651. void Window::window_menu_activate_default()
  652. {
  653. ensure_window_menu();
  654. m_window_menu->activate_default();
  655. }
  656. void Window::request_close()
  657. {
  658. Event close_request(Event::WindowCloseRequest);
  659. event(close_request);
  660. }
  661. void Window::set_fullscreen(bool fullscreen)
  662. {
  663. if (m_fullscreen == fullscreen)
  664. return;
  665. m_fullscreen = fullscreen;
  666. Gfx::IntRect new_window_rect = m_rect;
  667. if (m_fullscreen) {
  668. m_saved_nonfullscreen_rect = m_rect;
  669. new_window_rect = Screen::the().rect();
  670. } else if (!m_saved_nonfullscreen_rect.is_empty()) {
  671. new_window_rect = m_saved_nonfullscreen_rect;
  672. }
  673. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(new_window_rect));
  674. set_rect(new_window_rect);
  675. }
  676. Gfx::IntRect Window::tiled_rect(WindowTileType tiled) const
  677. {
  678. VERIFY(tiled != WindowTileType::None);
  679. int frame_width = (m_frame.rect().width() - m_rect.width()) / 2;
  680. int title_bar_height = m_frame.title_bar_rect().height();
  681. int menu_height = WindowManager::the().maximized_window_rect(*this).y();
  682. int max_height = WindowManager::the().maximized_window_rect(*this).height();
  683. switch (tiled) {
  684. case WindowTileType::Left:
  685. return Gfx::IntRect(0,
  686. menu_height,
  687. Screen::the().width() / 2 - frame_width,
  688. max_height);
  689. case WindowTileType::Right:
  690. return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
  691. menu_height,
  692. Screen::the().width() / 2 - frame_width,
  693. max_height);
  694. case WindowTileType::Top:
  695. return Gfx::IntRect(0,
  696. menu_height,
  697. Screen::the().width() - frame_width,
  698. (max_height - title_bar_height) / 2 - frame_width);
  699. case WindowTileType::Bottom:
  700. return Gfx::IntRect(0,
  701. menu_height + (title_bar_height + max_height) / 2 + frame_width,
  702. Screen::the().width() - frame_width,
  703. (max_height - title_bar_height) / 2 - frame_width);
  704. case WindowTileType::TopLeft:
  705. return Gfx::IntRect(0,
  706. menu_height,
  707. Screen::the().width() / 2 - frame_width,
  708. (max_height - title_bar_height) / 2 - frame_width);
  709. case WindowTileType::TopRight:
  710. return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
  711. menu_height,
  712. Screen::the().width() / 2 - frame_width,
  713. (max_height - title_bar_height) / 2 - frame_width);
  714. case WindowTileType::BottomLeft:
  715. return Gfx::IntRect(0,
  716. menu_height + (title_bar_height + max_height) / 2 + frame_width,
  717. Screen::the().width() / 2 - frame_width,
  718. (max_height - title_bar_height) / 2);
  719. case WindowTileType::BottomRight:
  720. return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
  721. menu_height + (title_bar_height + max_height) / 2 + frame_width,
  722. Screen::the().width() / 2 - frame_width,
  723. (max_height - title_bar_height) / 2);
  724. default:
  725. VERIFY_NOT_REACHED();
  726. }
  727. }
  728. bool Window::set_untiled(Optional<Gfx::IntPoint> fixed_point)
  729. {
  730. if (m_tiled == WindowTileType::None)
  731. return false;
  732. VERIFY(!resize_aspect_ratio().has_value());
  733. m_tiled = WindowTileType::None;
  734. if (fixed_point.has_value()) {
  735. auto new_rect = Gfx::IntRect(m_rect);
  736. new_rect.set_size_around(m_untiled_rect.size(), fixed_point.value());
  737. set_rect(new_rect);
  738. } else {
  739. set_rect(m_untiled_rect);
  740. }
  741. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  742. return true;
  743. }
  744. void Window::set_tiled(WindowTileType tiled)
  745. {
  746. VERIFY(tiled != WindowTileType::None);
  747. if (m_tiled == tiled)
  748. return;
  749. if (resize_aspect_ratio().has_value())
  750. return;
  751. if (m_tiled == WindowTileType::None)
  752. m_untiled_rect = m_rect;
  753. m_tiled = tiled;
  754. set_rect(tiled_rect(tiled));
  755. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  756. }
  757. void Window::detach_client(Badge<ClientConnection>)
  758. {
  759. m_client = nullptr;
  760. }
  761. void Window::recalculate_rect()
  762. {
  763. if (!is_resizable())
  764. return;
  765. bool send_event = true;
  766. if (m_tiled != WindowTileType::None) {
  767. set_rect(tiled_rect(m_tiled));
  768. } else if (is_maximized()) {
  769. set_rect(WindowManager::the().maximized_window_rect(*this));
  770. } else if (type() == WindowType::Desktop) {
  771. set_rect(WindowManager::the().desktop_rect());
  772. } else {
  773. send_event = false;
  774. }
  775. if (send_event) {
  776. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  777. }
  778. }
  779. void Window::add_child_window(Window& child_window)
  780. {
  781. m_child_windows.append(child_window);
  782. }
  783. void Window::add_accessory_window(Window& accessory_window)
  784. {
  785. m_accessory_windows.append(accessory_window);
  786. }
  787. void Window::set_parent_window(Window& parent_window)
  788. {
  789. VERIFY(!m_parent_window);
  790. m_parent_window = parent_window;
  791. if (m_accessory)
  792. parent_window.add_accessory_window(*this);
  793. else
  794. parent_window.add_child_window(*this);
  795. }
  796. bool Window::is_accessory() const
  797. {
  798. if (!m_accessory)
  799. return false;
  800. if (parent_window() != nullptr)
  801. return true;
  802. // If accessory window was unparented, convert to a regular window
  803. const_cast<Window*>(this)->set_accessory(false);
  804. return false;
  805. }
  806. bool Window::is_accessory_of(Window& window) const
  807. {
  808. if (!is_accessory())
  809. return false;
  810. return parent_window() == &window;
  811. }
  812. void Window::modal_unparented()
  813. {
  814. m_modal = false;
  815. WindowManager::the().notify_modal_unparented(*this);
  816. }
  817. bool Window::is_modal() const
  818. {
  819. if (!m_modal)
  820. return false;
  821. if (!m_parent_window) {
  822. const_cast<Window*>(this)->modal_unparented();
  823. return false;
  824. }
  825. return true;
  826. }
  827. void Window::set_progress(int progress)
  828. {
  829. if (m_progress == progress)
  830. return;
  831. m_progress = progress;
  832. WindowManager::the().notify_progress_changed(*this);
  833. }
  834. bool Window::is_descendant_of(Window& window) const
  835. {
  836. for (auto* parent = parent_window(); parent; parent = parent->parent_window()) {
  837. if (parent == &window)
  838. return true;
  839. for (auto& accessory : parent->accessory_windows()) {
  840. if (accessory == &window)
  841. return true;
  842. }
  843. }
  844. return false;
  845. }
  846. bool Window::hit_test(const Gfx::IntPoint& point, bool include_frame) const
  847. {
  848. if (!frame().rect().contains(point))
  849. return false;
  850. if (!rect().contains(point)) {
  851. if (include_frame)
  852. return frame().hit_test(point);
  853. return false;
  854. }
  855. if (!m_hit_testing_enabled)
  856. return false;
  857. u8 threshold = alpha_hit_threshold() * 255;
  858. if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel())
  859. return true;
  860. auto relative_point = point.translated(-rect().location()) * m_backing_store->scale();
  861. auto color = m_backing_store->get_pixel(relative_point);
  862. return color.alpha() >= threshold;
  863. }
  864. void Window::set_menubar(Menubar* menubar)
  865. {
  866. if (m_menubar == menubar)
  867. return;
  868. m_menubar = menubar;
  869. if (m_menubar) {
  870. // FIXME: Maybe move this to the theming system?
  871. static constexpr auto menubar_menu_margin = 14;
  872. auto& wm = WindowManager::the();
  873. Gfx::IntPoint next_menu_location { 0, 0 };
  874. auto menubar_rect = Gfx::WindowTheme::current().menubar_rect(Gfx::WindowTheme::WindowType::Normal, rect(), wm.palette(), 1);
  875. m_menubar->for_each_menu([&](Menu& menu) {
  876. int text_width = wm.font().width(Gfx::parse_ampersand_string(menu.name()));
  877. menu.set_rect_in_window_menubar({ next_menu_location.x(), 0, text_width + menubar_menu_margin, menubar_rect.height() });
  878. next_menu_location.move_by(menu.rect_in_window_menubar().width(), 0);
  879. return IterationDecision::Continue;
  880. });
  881. }
  882. Compositor::the().invalidate_occlusions();
  883. frame().invalidate();
  884. }
  885. void Window::invalidate_menubar()
  886. {
  887. if (!m_should_show_menubar || !menubar())
  888. return;
  889. // FIXME: This invalidates way more than the menubar!
  890. frame().invalidate();
  891. }
  892. }