Serenity Operating System
at master 1005 lines 41 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "ConnectionFromClient.h" 8#include <AK/Badge.h> 9#include <LibGfx/Font/Font.h> 10#include <LibGfx/Painter.h> 11#include <LibGfx/StylePainter.h> 12#include <LibGfx/WindowTheme.h> 13#include <WindowServer/Button.h> 14#include <WindowServer/Compositor.h> 15#include <WindowServer/Event.h> 16#include <WindowServer/MultiScaleBitmaps.h> 17#include <WindowServer/Screen.h> 18#include <WindowServer/Window.h> 19#include <WindowServer/WindowFrame.h> 20#include <WindowServer/WindowManager.h> 21 22namespace WindowServer { 23 24static Gfx::WindowTheme::WindowType to_theme_window_type(WindowType type) 25{ 26 switch (type) { 27 case WindowType::Normal: 28 return Gfx::WindowTheme::WindowType::Normal; 29 case WindowType::Notification: 30 return Gfx::WindowTheme::WindowType::Notification; 31 default: 32 return Gfx::WindowTheme::WindowType::Other; 33 } 34} 35 36static Gfx::WindowTheme::WindowMode to_theme_window_mode(WindowMode mode) 37{ 38 switch (mode) { 39 case WindowMode::RenderAbove: 40 return Gfx::WindowTheme::WindowMode::RenderAbove; 41 default: 42 return Gfx::WindowTheme::WindowMode::Other; 43 } 44} 45 46static Button::Icon s_minimize_icon; 47static Button::Icon s_maximize_icon; 48static Button::Icon s_restore_icon; 49static Button::Icon s_close_icon; 50static Button::Icon s_close_modified_icon; 51 52static RefPtr<MultiScaleBitmaps> s_active_window_shadow; 53static RefPtr<MultiScaleBitmaps> s_inactive_window_shadow; 54static RefPtr<MultiScaleBitmaps> s_menu_shadow; 55static RefPtr<MultiScaleBitmaps> s_taskbar_shadow; 56static RefPtr<MultiScaleBitmaps> s_tooltip_shadow; 57static DeprecatedString s_last_active_window_shadow_path; 58static DeprecatedString s_last_inactive_window_shadow_path; 59static DeprecatedString s_last_menu_shadow_path; 60static DeprecatedString s_last_taskbar_shadow_path; 61static DeprecatedString s_last_tooltip_shadow_path; 62 63static Gfx::IntRect frame_rect_for_window(Window& window, Gfx::IntRect const& rect) 64{ 65 if (window.is_frameless()) 66 return rect; 67 int menu_row_count = (window.menubar().has_menus() && window.should_show_menubar()) ? 1 : 0; 68 return Gfx::WindowTheme::current().frame_rect_for_window(to_theme_window_type(window.type()), to_theme_window_mode(window.mode()), rect, WindowManager::the().palette(), menu_row_count); 69} 70 71WindowFrame::WindowFrame(Window& window) 72 : m_window(window) 73{ 74 // Because Window constructs a WindowFrame during its construction, we need 75 // to be careful and defer doing initialization that assumes a fully 76 // constructed Window. It is fully constructed when Window notifies us with 77 // a call to WindowFrame::window_was_constructed. 78} 79 80void WindowFrame::window_was_constructed(Badge<Window>) 81{ 82 if (m_window.is_closeable()) { 83 auto button = make<Button>(*this, [this](auto&) { 84 m_window.handle_window_menu_action(WindowMenuAction::Close); 85 }); 86 m_close_button = button.ptr(); 87 m_buttons.append(move(button)); 88 } 89 90 if (m_window.is_resizable()) { 91 auto button = make<Button>(*this, [this](auto&) { 92 m_window.handle_window_menu_action(WindowMenuAction::MaximizeOrRestore); 93 }); 94 button->on_middle_click = [&](auto&) { 95 if (m_window.tile_type() == WindowTileType::VerticallyMaximized) 96 m_window.set_untiled(); 97 else 98 m_window.set_tiled(WindowTileType::VerticallyMaximized); 99 }; 100 button->on_secondary_click = [&](auto&) { 101 if (m_window.tile_type() == WindowTileType::HorizontallyMaximized) 102 m_window.set_untiled(); 103 else 104 m_window.set_tiled(WindowTileType::HorizontallyMaximized); 105 }; 106 m_maximize_button = button.ptr(); 107 m_buttons.append(move(button)); 108 } 109 110 if (m_window.is_minimizable() && !m_window.is_modal()) { 111 auto button = make<Button>(*this, [this](auto&) { 112 m_window.handle_window_menu_action(WindowMenuAction::MinimizeOrUnminimize); 113 }); 114 m_minimize_button = button.ptr(); 115 m_buttons.append(move(button)); 116 } 117 118 set_button_icons(); 119 120 m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); 121} 122 123WindowFrame::~WindowFrame() = default; 124 125void WindowFrame::set_button_icons() 126{ 127 set_dirty(); 128 if (m_window.is_frameless()) 129 return; 130 131 auto button_style = WindowManager::the().palette().title_buttons_icon_only() 132 ? Button::Style::IconOnly 133 : Button::Style::Normal; 134 135 if (m_window.is_closeable()) { 136 m_close_button->set_icon(m_window.is_modified() ? s_close_modified_icon : s_close_icon); 137 m_close_button->set_style(button_style); 138 } 139 if (m_window.is_minimizable() && !m_window.is_modal()) { 140 m_minimize_button->set_icon(s_minimize_icon); 141 m_minimize_button->set_style(button_style); 142 } 143 if (m_window.is_resizable()) { 144 m_maximize_button->set_icon(m_window.is_maximized() ? s_restore_icon : s_maximize_icon); 145 m_maximize_button->set_style(button_style); 146 } 147} 148 149void WindowFrame::reload_config() 150{ 151 DeprecatedString icons_path = WindowManager::the().palette().title_button_icons_path(); 152 153 auto reload_bitmap = [&](RefPtr<MultiScaleBitmaps>& multiscale_bitmap, StringView path, StringView default_path = ""sv) { 154 StringBuilder full_path; 155 full_path.append(icons_path); 156 full_path.append(path); 157 if (multiscale_bitmap) 158 multiscale_bitmap->load(full_path.string_view(), default_path); 159 else 160 multiscale_bitmap = MultiScaleBitmaps::create(full_path.string_view(), default_path); 161 }; 162 163 auto reload_icon = [&](Button::Icon& icon, StringView name, StringView default_path) { 164 StringBuilder full_name; 165 full_name.append(name); 166 full_name.append(".png"sv); 167 reload_bitmap(icon.bitmap, full_name.string_view(), default_path); 168 // Note: No default for hover bitmaps 169 full_name.clear(); 170 full_name.append(name); 171 full_name.append("-hover.png"sv); 172 reload_bitmap(icon.hover_bitmap, full_name.string_view()); 173 }; 174 reload_icon(s_minimize_icon, "window-minimize"sv, "/res/icons/16x16/downward-triangle.png"sv); 175 reload_icon(s_maximize_icon, "window-maximize"sv, "/res/icons/16x16/upward-triangle.png"sv); 176 reload_icon(s_restore_icon, "window-restore"sv, "/res/icons/16x16/window-restore.png"sv); 177 reload_icon(s_close_icon, "window-close"sv, "/res/icons/16x16/window-close.png"sv); 178 reload_icon(s_close_modified_icon, "window-close-modified"sv, "/res/icons/16x16/window-close-modified.png"sv); 179 180 auto load_shadow = [](DeprecatedString const& path, DeprecatedString& last_path, RefPtr<MultiScaleBitmaps>& shadow_bitmap) { 181 if (path.is_empty()) { 182 last_path = DeprecatedString::empty(); 183 shadow_bitmap = nullptr; 184 } else if (!shadow_bitmap || last_path != path) { 185 if (shadow_bitmap) 186 shadow_bitmap->load(path); 187 else 188 shadow_bitmap = MultiScaleBitmaps::create(path); 189 if (shadow_bitmap) 190 last_path = path; 191 else 192 last_path = DeprecatedString::empty(); 193 } 194 }; 195 load_shadow(WindowManager::the().palette().active_window_shadow_path(), s_last_active_window_shadow_path, s_active_window_shadow); 196 load_shadow(WindowManager::the().palette().inactive_window_shadow_path(), s_last_inactive_window_shadow_path, s_inactive_window_shadow); 197 load_shadow(WindowManager::the().palette().menu_shadow_path(), s_last_menu_shadow_path, s_menu_shadow); 198 load_shadow(WindowManager::the().palette().taskbar_shadow_path(), s_last_taskbar_shadow_path, s_taskbar_shadow); 199 load_shadow(WindowManager::the().palette().tooltip_shadow_path(), s_last_tooltip_shadow_path, s_tooltip_shadow); 200} 201 202MultiScaleBitmaps const* WindowFrame::shadow_bitmap() const 203{ 204 if (m_window.is_frameless() && !m_window.has_forced_shadow()) 205 return nullptr; 206 switch (m_window.type()) { 207 case WindowType::Desktop: 208 return nullptr; 209 case WindowType::Menu: 210 if (!WindowManager::the().system_effects().menu_shadow()) 211 return nullptr; 212 return s_menu_shadow; 213 case WindowType::Autocomplete: 214 case WindowType::Tooltip: 215 if (!WindowManager::the().system_effects().tooltip_shadow()) 216 return nullptr; 217 return s_tooltip_shadow; 218 case WindowType::Taskbar: 219 return s_taskbar_shadow; 220 case WindowType::AppletArea: 221 return nullptr; 222 case WindowType::WindowSwitcher: 223 return nullptr; 224 case WindowType::Popup: 225 if (!WindowManager::the().system_effects().window_shadow()) 226 return nullptr; 227 if (!m_window.has_forced_shadow()) 228 return nullptr; 229 return s_active_window_shadow; 230 default: 231 if (!WindowManager::the().system_effects().window_shadow()) 232 return nullptr; 233 // FIXME: Support shadow for themes with border radius 234 if (WindowManager::the().palette().window_border_radius() > 0) 235 return nullptr; 236 if (auto* highlight_window = WindowManager::the().highlight_window()) 237 return highlight_window == &m_window ? s_active_window_shadow : s_inactive_window_shadow; 238 return m_window.is_active() ? s_active_window_shadow : s_inactive_window_shadow; 239 } 240} 241 242bool WindowFrame::has_shadow() const 243{ 244 if (auto* shadow_bitmap = this->shadow_bitmap(); shadow_bitmap && shadow_bitmap->format() == Gfx::BitmapFormat::BGRA8888) 245 return true; 246 return false; 247} 248 249void WindowFrame::did_set_maximized(Badge<Window>, bool maximized) 250{ 251 VERIFY(m_maximize_button); 252 set_dirty(); 253 m_maximize_button->set_icon(maximized ? s_restore_icon : s_maximize_icon); 254} 255 256Gfx::IntRect WindowFrame::menubar_rect() const 257{ 258 if (!m_window.menubar().has_menus() || !m_window.should_show_menubar()) 259 return {}; 260 return Gfx::WindowTheme::current().menubar_rect(to_theme_window_type(m_window.type()), to_theme_window_mode(m_window.mode()), m_window.rect(), WindowManager::the().palette(), menu_row_count()); 261} 262 263Gfx::IntRect WindowFrame::titlebar_rect() const 264{ 265 return Gfx::WindowTheme::current().titlebar_rect(to_theme_window_type(m_window.type()), to_theme_window_mode(m_window.mode()), m_window.rect(), WindowManager::the().palette()); 266} 267 268Gfx::IntRect WindowFrame::titlebar_icon_rect() const 269{ 270 return Gfx::WindowTheme::current().titlebar_icon_rect(to_theme_window_type(m_window.type()), to_theme_window_mode(m_window.mode()), m_window.rect(), WindowManager::the().palette()); 271} 272 273Gfx::IntRect WindowFrame::titlebar_text_rect() const 274{ 275 return Gfx::WindowTheme::current().titlebar_text_rect(to_theme_window_type(m_window.type()), to_theme_window_mode(m_window.mode()), m_window.rect(), WindowManager::the().palette()); 276} 277 278Gfx::WindowTheme::WindowState WindowFrame::window_state_for_theme() const 279{ 280 auto& wm = WindowManager::the(); 281 282 if (m_flash_timer && m_flash_timer->is_active()) 283 return m_flash_counter & 1 ? Gfx::WindowTheme::WindowState::Highlighted : Gfx::WindowTheme::WindowState::Inactive; 284 285 if (&m_window == wm.highlight_window()) 286 return Gfx::WindowTheme::WindowState::Highlighted; 287 if (&m_window == wm.m_move_window) 288 return Gfx::WindowTheme::WindowState::Moving; 289 if (m_window.is_active()) 290 return Gfx::WindowTheme::WindowState::Active; 291 return Gfx::WindowTheme::WindowState::Inactive; 292} 293 294void WindowFrame::paint_notification_frame(Gfx::Painter& painter) 295{ 296 auto palette = WindowManager::the().palette(); 297 Gfx::WindowTheme::current().paint_notification_frame(painter, to_theme_window_mode(m_window.mode()), m_window.rect(), palette, m_buttons.last()->relative_rect()); 298} 299 300void WindowFrame::paint_menubar(Gfx::Painter& painter) 301{ 302 auto& wm = WindowManager::the(); 303 auto& font = wm.font(); 304 auto palette = wm.palette(); 305 auto menubar_rect = this->menubar_rect(); 306 307 painter.fill_rect(menubar_rect, palette.window()); 308 309 Gfx::PainterStateSaver saver(painter); 310 painter.add_clip_rect(menubar_rect); 311 painter.translate(menubar_rect.location()); 312 313 m_window.menubar().for_each_menu([&](Menu& menu) { 314 bool paint_as_flashed = ((&menu) == m_window.menubar().flashed_menu()); 315 if (paint_as_flashed) { 316 auto flashed_rect = menu.rect_in_window_menubar(); 317 flashed_rect.shrink(2, 2); 318 painter.fill_rect(flashed_rect, palette.selection()); 319 } 320 321 auto text_rect = menu.rect_in_window_menubar(); 322 Color text_color = (paint_as_flashed ? palette.selection_text() : palette.window_text()); 323 auto is_open = menu.is_open(); 324 if (is_open) 325 text_rect.translate_by(1, 1); 326 bool paint_as_pressed = is_open; 327 bool paint_as_hovered = !paint_as_pressed && &menu == MenuManager::the().hovered_menu(); 328 if (paint_as_pressed || paint_as_hovered) { 329 Gfx::StylePainter::paint_button(painter, menu.rect_in_window_menubar(), palette, Gfx::ButtonStyle::Coolbar, paint_as_pressed, paint_as_hovered); 330 } 331 painter.draw_ui_text(text_rect, menu.name(), font, Gfx::TextAlignment::Center, text_color); 332 return IterationDecision::Continue; 333 }); 334} 335 336void WindowFrame::paint_normal_frame(Gfx::Painter& painter) 337{ 338 auto palette = WindowManager::the().palette(); 339 Gfx::WindowTheme::current().paint_normal_frame(painter, window_state_for_theme(), to_theme_window_mode(m_window.mode()), m_window.rect(), m_window.computed_title(), m_window.icon(), palette, leftmost_titlebar_button_rect(), menu_row_count(), m_window.is_modified()); 340 341 if (m_window.menubar().has_menus() && m_window.should_show_menubar()) 342 paint_menubar(painter); 343} 344 345void WindowFrame::paint(Screen& screen, Gfx::Painter& painter, Gfx::IntRect const& rect) 346{ 347 if (auto* cached = render_to_cache(screen)) 348 cached->paint(*this, painter, rect); 349} 350 351void WindowFrame::PerScaleRenderedCache::paint(WindowFrame& frame, Gfx::Painter& painter, Gfx::IntRect const& rect) 352{ 353 auto frame_rect = frame.unconstrained_render_rect(); 354 auto window_rect = frame.window().rect(); 355 if (m_top_bottom) { 356 auto top_bottom_height = frame_rect.height() - window_rect.height(); 357 if (m_bottom_y > 0) { 358 // We have a top piece 359 auto src_rect = rect.intersected(Gfx::Rect { frame_rect.location(), { frame_rect.width(), m_bottom_y } }); 360 if (!src_rect.is_empty()) 361 painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), frame.opacity()); 362 } 363 if (m_bottom_y < top_bottom_height) { 364 // We have a bottom piece 365 Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.bottom() + 1, frame_rect.width(), top_bottom_height - m_bottom_y }; 366 auto src_rect = rect.intersected(rect_in_frame); 367 if (!src_rect.is_empty()) 368 painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-rect_in_frame.x(), -rect_in_frame.y() + m_bottom_y), frame.opacity()); 369 } 370 } 371 372 if (m_left_right) { 373 auto left_right_width = frame_rect.width() - window_rect.width(); 374 if (m_right_x > 0) { 375 // We have a left piece 376 Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.y(), m_right_x, window_rect.height() }; 377 auto src_rect = rect.intersected(rect_in_frame); 378 if (!src_rect.is_empty()) 379 painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.location()), frame.opacity()); 380 } 381 if (m_right_x < left_right_width) { 382 // We have a right piece 383 Gfx::IntRect rect_in_frame { window_rect.right() + 1, window_rect.y(), left_right_width - m_right_x, window_rect.height() }; 384 auto src_rect = rect.intersected(rect_in_frame); 385 if (!src_rect.is_empty()) 386 painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.x() + m_right_x, -rect_in_frame.y()), frame.opacity()); 387 } 388 } 389} 390 391void WindowFrame::render(Screen& screen, Gfx::Painter& painter) 392{ 393 if (m_window.is_frameless()) 394 return; 395 396 if (m_window.type() == WindowType::Notification) 397 paint_notification_frame(painter); 398 else if (m_window.type() == WindowType::Normal) 399 paint_normal_frame(painter); 400 else 401 return; 402 403 for (auto& button : m_buttons) 404 button->paint(screen, painter); 405} 406 407void WindowFrame::theme_changed() 408{ 409 m_rendered_cache = {}; 410 411 layout_buttons(); 412 set_button_icons(); 413 414 m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); 415} 416 417auto WindowFrame::render_to_cache(Screen& screen) -> PerScaleRenderedCache* 418{ 419 auto scale = screen.scale_factor(); 420 PerScaleRenderedCache* rendered_cache; 421 auto cached_it = m_rendered_cache.find(scale); 422 if (cached_it == m_rendered_cache.end()) { 423 auto new_rendered_cache = make<PerScaleRenderedCache>(); 424 rendered_cache = new_rendered_cache.ptr(); 425 m_rendered_cache.set(scale, move(new_rendered_cache)); 426 } else { 427 rendered_cache = cached_it->value.ptr(); 428 } 429 rendered_cache->render(*this, screen); 430 return rendered_cache; 431} 432 433void WindowFrame::PerScaleRenderedCache::render(WindowFrame& frame, Screen& screen) 434{ 435 if (!m_dirty) 436 return; 437 m_dirty = false; 438 439 auto scale = screen.scale_factor(); 440 441 auto frame_rect = frame.rect(); 442 443 auto frame_rect_including_shadow = frame_rect; 444 auto* shadow_bitmap = frame.shadow_bitmap(); 445 Gfx::IntPoint shadow_offset; 446 447 if (shadow_bitmap) { 448 auto total_shadow_size = shadow_bitmap->bitmap(screen.scale_factor()).height(); 449 frame_rect_including_shadow.inflate(total_shadow_size, total_shadow_size); 450 auto offset = total_shadow_size / 2; 451 shadow_offset = { offset, offset }; 452 } 453 454 auto window_rect = frame.window().rect(); 455 456 // TODO: if we stop using a scaling factor we should clear cached bitmaps from this map 457 static HashMap<int, RefPtr<Gfx::Bitmap>> s_tmp_bitmap_cache; 458 Gfx::Bitmap* tmp_bitmap; 459 { 460 auto tmp_it = s_tmp_bitmap_cache.find(scale); 461 if (tmp_it == s_tmp_bitmap_cache.end() || !tmp_it->value->size().contains(frame_rect_including_shadow.size())) { 462 // Explicitly clear the old bitmap first so this works on machines with very little memory 463 if (tmp_it != s_tmp_bitmap_cache.end()) 464 tmp_it->value = nullptr; 465 466 auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, frame_rect_including_shadow.size(), scale); 467 if (bitmap_or_error.is_error()) { 468 s_tmp_bitmap_cache.remove(scale); 469 dbgln("Could not create bitmap of size {}: {}", frame_rect_including_shadow.size(), bitmap_or_error.error()); 470 return; 471 } 472 auto bitmap = bitmap_or_error.release_value(); 473 tmp_bitmap = bitmap.ptr(); 474 if (tmp_it != s_tmp_bitmap_cache.end()) 475 tmp_it->value = move(bitmap); 476 else 477 s_tmp_bitmap_cache.set(scale, move(bitmap)); 478 } else { 479 tmp_bitmap = tmp_it->value.ptr(); 480 } 481 } 482 483 VERIFY(tmp_bitmap); 484 485 auto top_bottom_height = frame_rect_including_shadow.height() - window_rect.height(); 486 auto left_right_width = frame_rect_including_shadow.width() - window_rect.width(); 487 488 if (!m_top_bottom || m_top_bottom->width() != frame_rect_including_shadow.width() || m_top_bottom->height() != top_bottom_height || m_top_bottom->scale() != scale) { 489 if (top_bottom_height > 0) 490 m_top_bottom = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { frame_rect_including_shadow.width(), top_bottom_height }, scale).release_value_but_fixme_should_propagate_errors(); 491 else 492 m_top_bottom = nullptr; 493 m_shadow_dirty = true; 494 } 495 if (!m_left_right || m_left_right->height() != frame_rect_including_shadow.height() || m_left_right->width() != left_right_width || m_left_right->scale() != scale) { 496 if (left_right_width > 0) 497 m_left_right = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { left_right_width, frame_rect_including_shadow.height() }, scale).release_value_but_fixme_should_propagate_errors(); 498 else 499 m_left_right = nullptr; 500 m_shadow_dirty = true; 501 } 502 503 auto& frame_rect_to_update = m_shadow_dirty ? frame_rect_including_shadow : frame_rect; 504 Gfx::IntPoint update_location(m_shadow_dirty ? Gfx::IntPoint { 0, 0 } : shadow_offset); 505 506 Gfx::Painter painter(*tmp_bitmap); 507 508 // Clear the frame area, not including the window content area, which we don't care about 509 for (auto& rect : frame_rect_to_update.shatter(window_rect)) 510 painter.clear_rect({ rect.location() - frame_rect_to_update.location(), rect.size() }, { 255, 255, 255, 0 }); 511 512 if (m_shadow_dirty && shadow_bitmap) 513 Gfx::StylePainter::paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, shadow_bitmap->bitmap(screen.scale_factor())); 514 515 { 516 Gfx::PainterStateSaver save(painter); 517 painter.translate(shadow_offset); 518 frame.render(screen, painter); 519 } 520 521 if (m_top_bottom && top_bottom_height > 0) { 522 m_bottom_y = window_rect.y() - frame_rect_including_shadow.y(); 523 VERIFY(m_bottom_y >= 0); 524 525 Gfx::Painter top_bottom_painter(*m_top_bottom); 526 top_bottom_painter.add_clip_rect({ update_location, { frame_rect_to_update.width(), top_bottom_height - update_location.y() - (frame_rect_including_shadow.bottom() - frame_rect_to_update.bottom()) } }); 527 if (m_bottom_y > 0) 528 top_bottom_painter.blit({ 0, 0 }, *tmp_bitmap, { 0, 0, frame_rect_including_shadow.width(), m_bottom_y }, 1.0, false); 529 if (m_bottom_y < top_bottom_height) 530 top_bottom_painter.blit({ 0, m_bottom_y }, *tmp_bitmap, { 0, frame_rect_including_shadow.height() - (frame_rect_including_shadow.bottom() - window_rect.bottom()), frame_rect_including_shadow.width(), top_bottom_height - m_bottom_y }, 1.0, false); 531 } else { 532 m_bottom_y = 0; 533 } 534 535 if (left_right_width > 0) { 536 m_right_x = window_rect.x() - frame_rect_including_shadow.x(); 537 VERIFY(m_right_x >= 0); 538 539 Gfx::Painter left_right_painter(*m_left_right); 540 left_right_painter.add_clip_rect({ update_location, { left_right_width - update_location.x() - (frame_rect_including_shadow.right() - frame_rect_to_update.right()), window_rect.height() } }); 541 if (m_right_x > 0) 542 left_right_painter.blit({ 0, 0 }, *tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }, 1.0, false); 543 if (m_right_x < left_right_width) 544 left_right_painter.blit({ m_right_x, 0 }, *tmp_bitmap, { (window_rect.right() - frame_rect_including_shadow.x()) + 1, m_bottom_y, frame_rect_including_shadow.width() - (frame_rect_including_shadow.right() - window_rect.right()), window_rect.height() }, 1.0, false); 545 } else { 546 m_right_x = 0; 547 } 548 549 m_shadow_dirty = false; 550} 551 552void WindowFrame::set_opacity(float opacity) 553{ 554 if (m_opacity == opacity) 555 return; 556 bool was_opaque = is_opaque(); 557 m_opacity = opacity; 558 if (was_opaque != is_opaque()) 559 Compositor::the().invalidate_occlusions(); 560 Compositor::the().invalidate_screen(render_rect()); 561 WindowManager::the().notify_opacity_changed(m_window); 562} 563 564Gfx::IntRect WindowFrame::inflated_for_shadow(Gfx::IntRect const& frame_rect) const 565{ 566 if (auto* shadow = shadow_bitmap()) { 567 auto total_shadow_size = shadow->default_bitmap().height(); 568 return frame_rect.inflated(total_shadow_size, total_shadow_size); 569 } 570 return frame_rect; 571} 572 573Gfx::IntRect WindowFrame::rect() const 574{ 575 return frame_rect_for_window(m_window, m_window.rect()); 576} 577 578Gfx::IntRect WindowFrame::constrained_render_rect_to_screen(Gfx::IntRect const& render_rect) const 579{ 580 if (m_window.is_tiled()) 581 return render_rect.intersected(Screen::closest_to_rect(rect()).rect()); 582 return render_rect; 583} 584 585Gfx::IntRect WindowFrame::leftmost_titlebar_button_rect() const 586{ 587 if (!m_buttons.is_empty()) 588 return m_buttons.last()->relative_rect(); 589 590 auto rect = titlebar_rect(); 591 rect.translate_by(rect.width(), 0); 592 return rect; 593} 594 595Gfx::IntRect WindowFrame::render_rect() const 596{ 597 return constrained_render_rect_to_screen(inflated_for_shadow(rect())); 598} 599 600Gfx::IntRect WindowFrame::unconstrained_render_rect() const 601{ 602 return inflated_for_shadow(rect()); 603} 604 605Gfx::DisjointIntRectSet WindowFrame::opaque_render_rects() const 606{ 607 auto border_radius = WindowManager::the().palette().window_border_radius(); 608 if (has_alpha_channel() || border_radius > 0) { 609 if (m_window.is_opaque()) 610 return constrained_render_rect_to_screen(m_window.rect()); 611 return {}; 612 } 613 if (m_window.is_opaque()) 614 return constrained_render_rect_to_screen(rect()); 615 Gfx::DisjointIntRectSet opaque_rects; 616 opaque_rects.add_many(constrained_render_rect_to_screen(rect()).shatter(m_window.rect())); 617 return opaque_rects; 618} 619 620Gfx::DisjointIntRectSet WindowFrame::transparent_render_rects() const 621{ 622 auto border_radius = WindowManager::the().palette().window_border_radius(); 623 if (has_alpha_channel() || border_radius > 0) { 624 if (m_window.is_opaque()) { 625 Gfx::DisjointIntRectSet transparent_rects; 626 transparent_rects.add_many(render_rect().shatter(m_window.rect())); 627 return transparent_rects; 628 } 629 return render_rect(); 630 } 631 632 auto total_render_rect = render_rect(); 633 Gfx::DisjointIntRectSet transparent_rects; 634 if (has_shadow()) 635 transparent_rects.add_many(total_render_rect.shatter(rect())); 636 if (!m_window.is_opaque()) 637 transparent_rects.add(m_window.rect().intersected(total_render_rect)); 638 return transparent_rects; 639} 640 641void WindowFrame::invalidate_titlebar() 642{ 643 set_dirty(); 644 invalidate(titlebar_rect()); 645} 646 647void WindowFrame::invalidate() 648{ 649 auto frame_rect = render_rect(); 650 invalidate(Gfx::IntRect { frame_rect.location() - m_window.position(), frame_rect.size() }); 651 m_window.invalidate(true, true); 652} 653 654void WindowFrame::invalidate_menubar() 655{ 656 invalidate(menubar_rect()); 657} 658 659void WindowFrame::invalidate(Gfx::IntRect relative_rect) 660{ 661 auto frame_rect = rect(); 662 auto window_rect = m_window.rect(); 663 relative_rect.translate_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y()); 664 set_dirty(); 665 m_window.invalidate(relative_rect, true); 666} 667 668void WindowFrame::window_rect_changed(Gfx::IntRect const& old_rect, Gfx::IntRect const& new_rect) 669{ 670 layout_buttons(); 671 672 set_dirty(true); 673 WindowManager::the().notify_rect_changed(m_window, old_rect, new_rect); 674} 675 676void WindowFrame::layout_buttons() 677{ 678 auto button_rects = Gfx::WindowTheme::current().layout_buttons(to_theme_window_type(m_window.type()), to_theme_window_mode(m_window.mode()), m_window.rect(), WindowManager::the().palette(), m_buttons.size()); 679 for (size_t i = 0; i < m_buttons.size(); i++) 680 m_buttons[i]->set_relative_rect(button_rects[i]); 681} 682 683Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint position) 684{ 685 if (m_window.is_frameless() || m_window.is_fullscreen()) 686 return {}; 687 if (!constrained_render_rect_to_screen(rect()).contains(position)) { 688 // Checking just frame_rect is not enough. If we constrain rendering 689 // a window to one screen (e.g. when it's maximized or tiled) so that 690 // the frame doesn't bleed into the adjacent screen(s), then we need 691 // to also check that we're within these bounds. 692 return {}; 693 } 694 auto window_rect = m_window.rect(); 695 if (window_rect.contains(position)) 696 return {}; 697 698 auto* screen = Screen::find_by_location(position); 699 if (!screen) 700 return {}; 701 auto* cached = render_to_cache(*screen); 702 if (!cached) 703 return {}; 704 705 auto window_relative_position = position.translated(-unconstrained_render_rect().location()); 706 return cached->hit_test(*this, position, window_relative_position); 707} 708 709Optional<HitTestResult> WindowFrame::PerScaleRenderedCache::hit_test(WindowFrame& frame, Gfx::IntPoint position, Gfx::IntPoint window_relative_position) 710{ 711 HitTestResult result { 712 .window = frame.window(), 713 .screen_position = position, 714 .window_relative_position = window_relative_position, 715 .is_frame_hit = true, 716 }; 717 718 u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(frame.window_state_for_theme()) * 255; 719 if (alpha_threshold == 0) 720 return result; 721 u8 alpha = 0xff; 722 723 auto window_rect = frame.window().rect(); 724 if (position.y() < window_rect.y()) { 725 if (m_top_bottom) { 726 auto scaled_relative_point = window_relative_position * m_top_bottom->scale(); 727 if (m_top_bottom->rect().contains(scaled_relative_point)) 728 alpha = m_top_bottom->get_pixel(scaled_relative_point).alpha(); 729 } 730 } else if (position.y() > window_rect.bottom()) { 731 if (m_top_bottom) { 732 Gfx::IntPoint scaled_relative_point { window_relative_position.x() * m_top_bottom->scale(), m_bottom_y * m_top_bottom->scale() + position.y() - window_rect.bottom() - 1 }; 733 if (m_top_bottom->rect().contains(scaled_relative_point)) 734 alpha = m_top_bottom->get_pixel(scaled_relative_point).alpha(); 735 } 736 } else if (position.x() < window_rect.x()) { 737 if (m_left_right) { 738 Gfx::IntPoint scaled_relative_point { window_relative_position.x() * m_left_right->scale(), (window_relative_position.y() - m_bottom_y) * m_left_right->scale() }; 739 if (m_left_right->rect().contains(scaled_relative_point)) 740 alpha = m_left_right->get_pixel(scaled_relative_point).alpha(); 741 } 742 } else if (position.x() > window_rect.right()) { 743 if (m_left_right) { 744 Gfx::IntPoint scaled_relative_point { m_right_x * m_left_right->scale() + position.x() - window_rect.right() - 1, (window_relative_position.y() - m_bottom_y) * m_left_right->scale() }; 745 if (m_left_right->rect().contains(scaled_relative_point)) 746 alpha = m_left_right->get_pixel(scaled_relative_point).alpha(); 747 } 748 } else { 749 return {}; 750 } 751 if (alpha >= alpha_threshold) 752 return result; 753 return {}; 754} 755 756bool WindowFrame::handle_titlebar_icon_mouse_event(MouseEvent const& event) 757{ 758 auto& wm = WindowManager::the(); 759 760 if (event.type() == Event::MouseDown && (event.button() == MouseButton::Primary || event.button() == MouseButton::Secondary)) { 761 // Manually start a potential double click. Since we're opening 762 // a menu, we will only receive the MouseDown event, so we 763 // need to record that fact. If the user subsequently clicks 764 // on the same area, the menu will get closed, and we will 765 // receive a MouseUp event, but because windows have changed 766 // we don't get a MouseDoubleClick event. We can however record 767 // this click, and when we receive the MouseUp event check if 768 // it would have been considered a double click, if it weren't 769 // for the fact that we opened and closed a window in the meanwhile 770 wm.system_menu_doubleclick(m_window, event); 771 772 m_window.popup_window_menu(titlebar_rect().bottom_left().translated(rect().location()), WindowMenuDefaultAction::Close); 773 return true; 774 } else if (event.type() == Event::MouseUp && event.button() == MouseButton::Primary) { 775 // Since the MouseDown event opened a menu, another MouseUp 776 // from the second click outside the menu wouldn't be considered 777 // a double click, so let's manually check if it would otherwise 778 // have been be considered to be one 779 if (wm.is_menu_doubleclick(m_window, event)) { 780 // It is a double click, so perform activate the default item 781 m_window.window_menu_activate_default(); 782 } 783 return true; 784 } 785 return false; 786} 787 788void WindowFrame::handle_titlebar_mouse_event(MouseEvent const& event) 789{ 790 auto& wm = WindowManager::the(); 791 792 if (titlebar_icon_rect().contains(event.position())) { 793 if (handle_titlebar_icon_mouse_event(event)) 794 return; 795 } 796 797 for (auto& button : m_buttons) { 798 if (button->relative_rect().contains(event.position())) 799 return button->on_mouse_event(event.translated(-button->relative_rect().location())); 800 } 801 802 if (event.type() == Event::MouseDown) { 803 if (m_window.type() == WindowType::Normal && event.button() == MouseButton::Secondary) { 804 auto default_action = m_window.is_maximized() ? WindowMenuDefaultAction::Restore : WindowMenuDefaultAction::Maximize; 805 m_window.popup_window_menu(event.position().translated(rect().location()), default_action); 806 return; 807 } 808 if (m_window.is_movable() && event.button() == MouseButton::Primary) 809 wm.start_window_move(m_window, event.translated(rect().location())); 810 } 811} 812 813void WindowFrame::handle_mouse_event(MouseEvent const& event) 814{ 815 VERIFY(!m_window.is_fullscreen()); 816 817 if (m_window.type() != WindowType::Normal && m_window.type() != WindowType::Notification) 818 return; 819 820 auto& wm = WindowManager::the(); 821 if (m_window.type() == WindowType::Normal) { 822 if (event.type() == Event::MouseDown) 823 wm.move_to_front_and_make_active(m_window); 824 } 825 826 if (m_window.blocking_modal_window()) 827 return; 828 829 // This is slightly hackish, but expand the title bar rect by two pixels downwards, 830 // so that mouse events between the title bar and window contents don't act like 831 // mouse events on the border. 832 auto adjusted_titlebar_rect = titlebar_rect(); 833 adjusted_titlebar_rect.set_height(adjusted_titlebar_rect.height() + 2); 834 835 if (adjusted_titlebar_rect.contains(event.position())) { 836 handle_titlebar_mouse_event(event); 837 return; 838 } 839 840 if (menubar_rect().contains(event.position())) { 841 handle_menubar_mouse_event(event); 842 return; 843 } 844 845 handle_border_mouse_event(event); 846} 847 848void WindowFrame::handle_border_mouse_event(MouseEvent const& event) 849{ 850 if (!m_window.is_resizable()) 851 return; 852 853 auto& wm = WindowManager::the(); 854 855 constexpr ResizeDirection direction_for_hot_area[3][3] = { 856 { ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight }, 857 { ResizeDirection::Left, ResizeDirection::None, ResizeDirection::Right }, 858 { ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight }, 859 }; 860 Gfx::IntRect outer_rect = { {}, rect().size() }; 861 VERIFY(outer_rect.contains(event.position())); 862 int window_relative_x = event.x() - outer_rect.x(); 863 int window_relative_y = event.y() - outer_rect.y(); 864 int corner_size = titlebar_rect().height(); 865 int hot_area_row = (window_relative_y < corner_size) ? 0 : (window_relative_y > outer_rect.height() - corner_size) ? 2 866 : 1; 867 int hot_area_column = (window_relative_x < corner_size) ? 0 : (window_relative_x > outer_rect.width() - corner_size) ? 2 868 : 1; 869 ResizeDirection resize_direction = direction_for_hot_area[hot_area_row][hot_area_column]; 870 871 // Double click latches a window's edge to the screen's edge 872 if (event.type() == Event::MouseDoubleClick) { 873 latch_window_to_screen_edge(resize_direction); 874 return; 875 } 876 877 if (event.type() == Event::MouseMove && event.buttons() == 0) { 878 wm.set_resize_candidate(m_window, resize_direction); 879 Compositor::the().invalidate_cursor(); 880 return; 881 } 882 883 if (event.type() == Event::MouseDown && event.button() == MouseButton::Primary) 884 wm.start_window_resize(m_window, event.translated(rect().location()), resize_direction); 885} 886 887void WindowFrame::handle_menubar_mouse_event(MouseEvent const& event) 888{ 889 Menu* hovered_menu = nullptr; 890 auto menubar_rect = this->menubar_rect(); 891 auto adjusted_position = event.position().translated(-menubar_rect.location()); 892 m_window.menubar().for_each_menu([&](Menu& menu) { 893 if (menu.rect_in_window_menubar().contains(adjusted_position)) { 894 hovered_menu = &menu; 895 handle_menu_mouse_event(menu, event); 896 return IterationDecision::Break; 897 } 898 return IterationDecision::Continue; 899 }); 900 if (!hovered_menu && event.type() == Event::Type::MouseDown) 901 MenuManager::the().close_everyone(); 902 if (hovered_menu != MenuManager::the().hovered_menu()) { 903 MenuManager::the().set_hovered_menu(hovered_menu); 904 invalidate(menubar_rect); 905 } 906} 907 908void WindowFrame::open_menubar_menu(Menu& menu) 909{ 910 auto menubar_rect = this->menubar_rect(); 911 MenuManager::the().close_everyone(); 912 auto position = menu.rect_in_window_menubar().bottom_left().translated(rect().location()).translated(menubar_rect.location()); 913 menu.set_unadjusted_position(position); 914 auto& window = menu.ensure_menu_window(position); 915 auto window_rect = window.rect(); 916 auto& screen = Screen::closest_to_rect(window_rect); 917 auto window_border_thickness = 1; 918 919 // If the menu is off the right edge of the screen align its right edge with the edge of the screen. 920 if (window_rect.right() > screen.width()) { 921 position = position.translated(((window_rect.right() - screen.width()) * -1) - window_border_thickness, 0); 922 } 923 // If the menu is below the bottom of the screen move it to appear above the menubar. 924 if (window_rect.bottom() > screen.height()) { 925 position = position.translated(0, (window_rect.height() * -1) - menubar_rect.height()); 926 } 927 928 window.set_rect(position.x(), position.y(), window_rect.width(), window_rect.height()); 929 930 MenuManager::the().open_menu(menu); 931 WindowManager::the().set_window_with_active_menu(&m_window); 932 invalidate(menubar_rect); 933} 934 935void WindowFrame::handle_menu_mouse_event(Menu& menu, MouseEvent const& event) 936{ 937 auto menubar_rect = this->menubar_rect(); 938 bool is_hover_with_any_menu_open = event.type() == MouseEvent::MouseMove && &m_window == WindowManager::the().window_with_active_menu(); 939 bool is_mousedown_with_left_button = event.type() == MouseEvent::MouseDown && event.button() == MouseButton::Primary; 940 bool should_open_menu = &menu != MenuManager::the().current_menu() && (is_hover_with_any_menu_open || is_mousedown_with_left_button); 941 bool should_close_menu = &menu == MenuManager::the().current_menu() && is_mousedown_with_left_button; 942 943 if (should_open_menu) { 944 open_menubar_menu(menu); 945 return; 946 } 947 948 if (should_close_menu) { 949 invalidate(menubar_rect); 950 MenuManager::the().close_everyone(); 951 } 952} 953 954void WindowFrame::start_flash_animation() 955{ 956 if (!m_flash_timer) { 957 m_flash_timer = Core::Timer::create_repeating(100, [this] { 958 VERIFY(m_flash_counter); 959 invalidate_titlebar(); 960 if (!--m_flash_counter) 961 m_flash_timer->stop(); 962 }).release_value_but_fixme_should_propagate_errors(); 963 } 964 m_flash_counter = 8; 965 m_flash_timer->start(); 966} 967 968int WindowFrame::menu_row_count() const 969{ 970 if (!m_window.should_show_menubar()) 971 return 0; 972 return m_window.menubar().has_menus() ? 1 : 0; 973} 974 975void WindowFrame::latch_window_to_screen_edge(ResizeDirection resize_direction) 976{ 977 auto window_rect = m_window.rect(); 978 auto frame_rect = rect(); 979 auto& screen = Screen::closest_to_rect(window_rect); 980 auto screen_rect = WindowManager::the().desktop_rect(screen); 981 982 if (resize_direction == ResizeDirection::UpLeft 983 || resize_direction == ResizeDirection::Up 984 || resize_direction == ResizeDirection::UpRight) 985 window_rect.inflate(frame_rect.top() - screen_rect.top(), 0, 0, 0); 986 987 if (resize_direction == ResizeDirection::UpRight 988 || resize_direction == ResizeDirection::Right 989 || resize_direction == ResizeDirection::DownRight) 990 window_rect.inflate(0, screen_rect.right() - frame_rect.right(), 0, 0); 991 992 if (resize_direction == ResizeDirection::DownLeft 993 || resize_direction == ResizeDirection::Down 994 || resize_direction == ResizeDirection::DownRight) 995 window_rect.inflate(0, 0, screen_rect.bottom() - frame_rect.bottom(), 0); 996 997 if (resize_direction == ResizeDirection::UpLeft 998 || resize_direction == ResizeDirection::Left 999 || resize_direction == ResizeDirection::DownLeft) 1000 window_rect.inflate(0, 0, 0, frame_rect.left() - screen_rect.left()); 1001 1002 m_window.set_rect(window_rect); 1003} 1004 1005}