mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 05:55:13 +00:00
The old `GUI::Window` resizing behavior created a new backing store for each resize event (i.e. every visible window size). This caused a lot of trashing and on my machine, caused up to 25% of CPU time spent in creating new backing stores. The new behavior is a bit more sensible: * If the window size is shrinking, the backing store is already large enough to contain the entire window - so we don't create a new one. * If the window size is growing, as soon as the backing store can no longer contain the window, it is inflated with a large margin (of an arbitrary chosen 64 pixels) in both directions to accommodate some leeway in resizing before an even larger backing store is required. * When the user stops resizing the window, the backing store is resized to the exact dimensions of the window. For me, this brings the CPU time for creating backing stores down to 0%.
1368 lines
43 KiB
C++
1368 lines
43 KiB
C++
/*
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Debug.h>
|
|
#include <AK/HashMap.h>
|
|
#include <AK/IDAllocator.h>
|
|
#include <AK/JsonObject.h>
|
|
#include <AK/NeverDestroyed.h>
|
|
#include <AK/ScopeGuard.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibCore/MimeData.h>
|
|
#include <LibGUI/Action.h>
|
|
#include <LibGUI/Application.h>
|
|
#include <LibGUI/ConnectionToWindowManagerServer.h>
|
|
#include <LibGUI/ConnectionToWindowServer.h>
|
|
#include <LibGUI/Desktop.h>
|
|
#include <LibGUI/Event.h>
|
|
#include <LibGUI/MenuItem.h>
|
|
#include <LibGUI/Menubar.h>
|
|
#include <LibGUI/Painter.h>
|
|
#include <LibGUI/Widget.h>
|
|
#include <LibGUI/Window.h>
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
namespace GUI {
|
|
|
|
static i32 s_next_backing_store_serial;
|
|
static IDAllocator s_window_id_allocator;
|
|
|
|
class WindowBackingStore {
|
|
public:
|
|
explicit WindowBackingStore(NonnullRefPtr<Gfx::Bitmap> bitmap)
|
|
: m_bitmap(move(bitmap))
|
|
, m_serial(++s_next_backing_store_serial)
|
|
, m_visible_size(m_bitmap->size())
|
|
{
|
|
}
|
|
|
|
Gfx::Bitmap& bitmap() { return *m_bitmap; }
|
|
Gfx::Bitmap const& bitmap() const { return *m_bitmap; }
|
|
|
|
Gfx::IntSize size() const { return m_bitmap->size(); }
|
|
|
|
i32 serial() const { return m_serial; }
|
|
|
|
Gfx::IntSize visible_size() const { return m_visible_size; }
|
|
void set_visible_size(Gfx::IntSize visible_size) { m_visible_size = visible_size; }
|
|
|
|
private:
|
|
NonnullRefPtr<Gfx::Bitmap> m_bitmap;
|
|
const i32 m_serial;
|
|
Gfx::IntSize m_visible_size;
|
|
};
|
|
|
|
static NeverDestroyed<HashTable<Window*>> all_windows;
|
|
static NeverDestroyed<HashMap<int, Window*>> reified_windows;
|
|
|
|
Window* Window::from_window_id(int window_id)
|
|
{
|
|
auto it = reified_windows->find(window_id);
|
|
if (it != reified_windows->end())
|
|
return (*it).value;
|
|
return nullptr;
|
|
}
|
|
|
|
Window::Window(Core::Object* parent)
|
|
: Core::Object(parent)
|
|
, m_menubar(Menubar::construct())
|
|
{
|
|
if (parent)
|
|
set_window_mode(WindowMode::Passive);
|
|
|
|
all_windows->set(this);
|
|
m_rect_when_windowless = { -5000, -5000, 0, 0 };
|
|
m_title_when_windowless = "GUI::Window";
|
|
|
|
register_property(
|
|
"title",
|
|
[this] { return title(); },
|
|
[this](auto& value) {
|
|
set_title(value.to_deprecated_string());
|
|
return true;
|
|
});
|
|
|
|
register_property("visible", [this] { return is_visible(); });
|
|
register_property("active", [this] { return is_active(); });
|
|
|
|
REGISTER_BOOL_PROPERTY("minimizable", is_minimizable, set_minimizable);
|
|
REGISTER_BOOL_PROPERTY("resizable", is_resizable, set_resizable);
|
|
REGISTER_BOOL_PROPERTY("fullscreen", is_fullscreen, set_fullscreen);
|
|
REGISTER_RECT_PROPERTY("rect", rect, set_rect);
|
|
REGISTER_SIZE_PROPERTY("base_size", base_size, set_base_size);
|
|
REGISTER_SIZE_PROPERTY("size_increment", size_increment, set_size_increment);
|
|
REGISTER_BOOL_PROPERTY("obey_widget_min_size", is_obeying_widget_min_size, set_obey_widget_min_size);
|
|
}
|
|
|
|
Window::~Window()
|
|
{
|
|
all_windows->remove(this);
|
|
hide();
|
|
}
|
|
|
|
void Window::close()
|
|
{
|
|
hide();
|
|
if (on_close)
|
|
on_close();
|
|
}
|
|
|
|
void Window::move_to_front()
|
|
{
|
|
if (!is_visible())
|
|
return;
|
|
|
|
ConnectionToWindowServer::the().async_move_window_to_front(m_window_id);
|
|
}
|
|
|
|
void Window::show()
|
|
{
|
|
if (is_visible())
|
|
return;
|
|
|
|
auto* parent_window = find_parent_window();
|
|
|
|
m_window_id = s_window_id_allocator.allocate();
|
|
|
|
Gfx::IntRect launch_origin_rect;
|
|
if (auto* launch_origin_rect_string = getenv("__libgui_launch_origin_rect")) {
|
|
auto parts = StringView { launch_origin_rect_string, strlen(launch_origin_rect_string) }.split_view(',');
|
|
if (parts.size() == 4) {
|
|
launch_origin_rect = Gfx::IntRect {
|
|
parts[0].to_int().value_or(0),
|
|
parts[1].to_int().value_or(0),
|
|
parts[2].to_int().value_or(0),
|
|
parts[3].to_int().value_or(0),
|
|
};
|
|
}
|
|
unsetenv("__libgui_launch_origin_rect");
|
|
}
|
|
|
|
update_min_size();
|
|
|
|
ConnectionToWindowServer::the().async_create_window(
|
|
m_window_id,
|
|
m_rect_when_windowless,
|
|
!m_moved_by_client,
|
|
m_has_alpha_channel,
|
|
m_minimizable,
|
|
m_closeable,
|
|
m_resizable,
|
|
m_fullscreen,
|
|
m_frameless,
|
|
m_forced_shadow,
|
|
m_opacity_when_windowless,
|
|
m_alpha_hit_threshold,
|
|
m_base_size,
|
|
m_size_increment,
|
|
m_minimum_size_when_windowless,
|
|
m_resize_aspect_ratio,
|
|
(i32)m_window_type,
|
|
(i32)m_window_mode,
|
|
m_title_when_windowless,
|
|
parent_window ? parent_window->window_id() : 0,
|
|
launch_origin_rect);
|
|
m_visible = true;
|
|
m_visible_for_timer_purposes = true;
|
|
|
|
apply_icon();
|
|
|
|
m_menubar->for_each_menu([&](Menu& menu) {
|
|
menu.realize_menu_if_needed();
|
|
ConnectionToWindowServer::the().async_add_menu(m_window_id, menu.menu_id());
|
|
return IterationDecision::Continue;
|
|
});
|
|
|
|
set_maximized(m_maximized);
|
|
reified_windows->set(m_window_id, this);
|
|
Application::the()->did_create_window({});
|
|
update();
|
|
}
|
|
|
|
Window* Window::find_parent_window()
|
|
{
|
|
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
|
|
if (is<Window>(ancestor))
|
|
return static_cast<Window*>(ancestor);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Window::server_did_destroy()
|
|
{
|
|
reified_windows->remove(m_window_id);
|
|
m_window_id = 0;
|
|
m_visible = false;
|
|
m_pending_paint_event_rects.clear();
|
|
m_back_store = nullptr;
|
|
m_front_store = nullptr;
|
|
m_cursor = Gfx::StandardCursor::None;
|
|
}
|
|
|
|
void Window::hide()
|
|
{
|
|
if (!is_visible())
|
|
return;
|
|
|
|
// NOTE: Don't bother asking WindowServer to destroy windows during application teardown.
|
|
// All our windows will be automatically garbage-collected by WindowServer anyway.
|
|
if (GUI::Application::in_teardown())
|
|
return;
|
|
|
|
m_rect_when_windowless = rect();
|
|
|
|
auto destroyed_window_ids = ConnectionToWindowServer::the().destroy_window(m_window_id);
|
|
server_did_destroy();
|
|
|
|
for (auto child_window_id : destroyed_window_ids) {
|
|
if (auto* window = Window::from_window_id(child_window_id)) {
|
|
window->server_did_destroy();
|
|
}
|
|
}
|
|
|
|
if (auto* app = Application::the()) {
|
|
bool app_has_visible_windows = false;
|
|
for (auto& window : *all_windows) {
|
|
if (window->is_visible()) {
|
|
app_has_visible_windows = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!app_has_visible_windows)
|
|
app->did_delete_last_window({});
|
|
}
|
|
}
|
|
|
|
void Window::set_title(DeprecatedString title)
|
|
{
|
|
m_title_when_windowless = move(title);
|
|
if (!is_visible())
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_window_title(m_window_id, m_title_when_windowless);
|
|
}
|
|
|
|
DeprecatedString Window::title() const
|
|
{
|
|
if (!is_visible())
|
|
return m_title_when_windowless;
|
|
return ConnectionToWindowServer::the().get_window_title(m_window_id);
|
|
}
|
|
|
|
Gfx::IntRect Window::applet_rect_on_screen() const
|
|
{
|
|
VERIFY(m_window_type == WindowType::Applet);
|
|
return ConnectionToWindowServer::the().get_applet_rect_on_screen(m_window_id);
|
|
}
|
|
|
|
Gfx::IntRect Window::rect() const
|
|
{
|
|
if (!is_visible())
|
|
return m_rect_when_windowless;
|
|
return ConnectionToWindowServer::the().get_window_rect(m_window_id);
|
|
}
|
|
|
|
void Window::set_rect(Gfx::IntRect const& a_rect)
|
|
{
|
|
if (a_rect.location() != m_rect_when_windowless.location()) {
|
|
m_moved_by_client = true;
|
|
}
|
|
|
|
m_rect_when_windowless = a_rect;
|
|
if (!is_visible()) {
|
|
if (m_main_widget)
|
|
m_main_widget->resize(m_rect_when_windowless.size());
|
|
return;
|
|
}
|
|
auto window_rect = ConnectionToWindowServer::the().set_window_rect(m_window_id, a_rect);
|
|
if (m_back_store && m_back_store->size() != window_rect.size())
|
|
m_back_store = nullptr;
|
|
if (m_front_store && m_front_store->size() != window_rect.size())
|
|
m_front_store = nullptr;
|
|
if (m_main_widget)
|
|
m_main_widget->resize(window_rect.size());
|
|
}
|
|
|
|
Gfx::IntSize Window::minimum_size() const
|
|
{
|
|
if (!is_visible())
|
|
return m_minimum_size_when_windowless;
|
|
|
|
return ConnectionToWindowServer::the().get_window_minimum_size(m_window_id);
|
|
}
|
|
|
|
void Window::set_minimum_size(Gfx::IntSize size)
|
|
{
|
|
VERIFY(size.width() >= 0 && size.height() >= 0);
|
|
VERIFY(!is_obeying_widget_min_size());
|
|
m_minimum_size_when_windowless = size;
|
|
|
|
if (is_visible())
|
|
ConnectionToWindowServer::the().async_set_window_minimum_size(m_window_id, size);
|
|
}
|
|
|
|
void Window::center_on_screen()
|
|
{
|
|
set_rect(rect().centered_within(Desktop::the().rect()));
|
|
}
|
|
|
|
void Window::center_within(Window const& other)
|
|
{
|
|
if (this == &other)
|
|
return;
|
|
set_rect(rect().centered_within(other.rect()));
|
|
}
|
|
|
|
void Window::set_window_type(WindowType window_type)
|
|
{
|
|
m_window_type = window_type;
|
|
}
|
|
|
|
void Window::set_window_mode(WindowMode mode)
|
|
{
|
|
VERIFY(!is_visible());
|
|
m_window_mode = mode;
|
|
}
|
|
|
|
void Window::make_window_manager(unsigned event_mask)
|
|
{
|
|
GUI::ConnectionToWindowManagerServer::the().async_set_event_mask(event_mask);
|
|
GUI::ConnectionToWindowManagerServer::the().async_set_manager_window(m_window_id);
|
|
}
|
|
|
|
bool Window::are_cursors_the_same(AK::Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> const& left, AK::Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> const& right) const
|
|
{
|
|
if (left.has<Gfx::StandardCursor>() != right.has<Gfx::StandardCursor>())
|
|
return false;
|
|
if (left.has<Gfx::StandardCursor>())
|
|
return left.get<Gfx::StandardCursor>() == right.get<Gfx::StandardCursor>();
|
|
return left.get<NonnullRefPtr<Gfx::Bitmap>>().ptr() == right.get<NonnullRefPtr<Gfx::Bitmap>>().ptr();
|
|
}
|
|
|
|
void Window::set_cursor(Gfx::StandardCursor cursor)
|
|
{
|
|
if (are_cursors_the_same(m_cursor, cursor))
|
|
return;
|
|
m_cursor = cursor;
|
|
update_cursor();
|
|
}
|
|
|
|
void Window::set_cursor(NonnullRefPtr<Gfx::Bitmap> cursor)
|
|
{
|
|
if (are_cursors_the_same(m_cursor, cursor))
|
|
return;
|
|
m_cursor = cursor;
|
|
update_cursor();
|
|
}
|
|
|
|
void Window::handle_drop_event(DropEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto result = m_main_widget->hit_test(event.position());
|
|
auto local_event = make<DropEvent>(result.local_position, event.text(), event.mime_data());
|
|
VERIFY(result.widget);
|
|
result.widget->dispatch_event(*local_event, this);
|
|
|
|
Application::the()->set_drag_hovered_widget({}, nullptr);
|
|
}
|
|
|
|
void Window::handle_mouse_event(MouseEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto result = m_main_widget->hit_test(event.position());
|
|
VERIFY(result.widget);
|
|
|
|
if (m_automatic_cursor_tracking_widget) {
|
|
auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect();
|
|
Gfx::IntPoint local_point { event.x() - window_relative_rect.x(), event.y() - window_relative_rect.y() };
|
|
auto local_event = MouseEvent((Event::Type)event.type(), local_point, event.buttons(), event.button(), event.modifiers(), event.wheel_delta_x(), event.wheel_delta_y(), event.wheel_raw_delta_x(), event.wheel_raw_delta_y());
|
|
m_automatic_cursor_tracking_widget->dispatch_event(local_event, this);
|
|
if (event.buttons() == 0) {
|
|
m_automatic_cursor_tracking_widget = nullptr;
|
|
} else {
|
|
auto is_hovered = m_automatic_cursor_tracking_widget.ptr() == result.widget.ptr();
|
|
set_hovered_widget(is_hovered ? m_automatic_cursor_tracking_widget.ptr() : nullptr);
|
|
}
|
|
return;
|
|
}
|
|
set_hovered_widget(result.widget);
|
|
if (event.buttons() != 0 && !m_automatic_cursor_tracking_widget)
|
|
m_automatic_cursor_tracking_widget = *result.widget;
|
|
auto local_event = MouseEvent((Event::Type)event.type(), result.local_position, event.buttons(), event.button(), event.modifiers(), event.wheel_delta_x(), event.wheel_delta_y(), event.wheel_raw_delta_x(), event.wheel_raw_delta_y());
|
|
result.widget->dispatch_event(local_event, this);
|
|
}
|
|
|
|
Gfx::IntSize Window::backing_store_size(Gfx::IntSize window_size) const
|
|
{
|
|
if (!m_resizing)
|
|
return window_size;
|
|
|
|
int const backing_margin_during_resize = 64;
|
|
return { window_size.width() + backing_margin_during_resize, window_size.height() + backing_margin_during_resize };
|
|
}
|
|
|
|
void Window::handle_multi_paint_event(MultiPaintEvent& event)
|
|
{
|
|
if (!is_visible())
|
|
return;
|
|
if (!m_main_widget)
|
|
return;
|
|
auto rects = event.rects();
|
|
if (!m_pending_paint_event_rects.is_empty()) {
|
|
// It's possible that there had been some calls to update() that
|
|
// haven't been flushed. We can handle these right now, avoiding
|
|
// another round trip.
|
|
rects.extend(move(m_pending_paint_event_rects));
|
|
}
|
|
VERIFY(!rects.is_empty());
|
|
|
|
// Throw away our backing store if its size is different, and we've stopped resizing or double buffering is disabled.
|
|
// This ensures that we shrink the backing store after a resize, and that we do not get flickering artifacts when
|
|
// directly painting into a shared active backing store.
|
|
if (m_back_store && (!m_resizing || !m_double_buffering_enabled) && m_back_store->size() != event.window_size())
|
|
m_back_store = nullptr;
|
|
|
|
// Discard our backing store if it's unable to contain the new window size. Smaller is fine though, that prevents
|
|
// lots of backing store allocations during a resize.
|
|
if (m_back_store && !m_back_store->size().contains(event.window_size()))
|
|
m_back_store = nullptr;
|
|
|
|
bool created_new_backing_store = false;
|
|
if (!m_back_store) {
|
|
m_back_store = create_backing_store(backing_store_size(event.window_size())).release_value_but_fixme_should_propagate_errors();
|
|
created_new_backing_store = true;
|
|
} else if (m_double_buffering_enabled) {
|
|
bool was_purged = false;
|
|
bool bitmap_has_memory = m_back_store->bitmap().set_nonvolatile(was_purged);
|
|
if (!bitmap_has_memory) {
|
|
// We didn't have enough memory to make the bitmap non-volatile!
|
|
// Fall back to single-buffered mode for this window.
|
|
// FIXME: Once we have a way to listen for system memory pressure notifications,
|
|
// it would be cool to transition back into double-buffered mode once
|
|
// the coast is clear.
|
|
dbgln("Not enough memory to make backing store non-volatile. Falling back to single-buffered mode.");
|
|
m_double_buffering_enabled = false;
|
|
m_back_store = move(m_front_store);
|
|
created_new_backing_store = true;
|
|
} else if (was_purged) {
|
|
// The backing store bitmap was cleared, but it does have memory.
|
|
// Act as if it's a new backing store so the entire window gets repainted.
|
|
created_new_backing_store = true;
|
|
}
|
|
}
|
|
|
|
if (created_new_backing_store) {
|
|
rects.clear();
|
|
rects.append({ {}, event.window_size() });
|
|
}
|
|
|
|
for (auto& rect : rects) {
|
|
PaintEvent paint_event(rect);
|
|
m_main_widget->dispatch_event(paint_event, this);
|
|
}
|
|
m_back_store->set_visible_size(event.window_size());
|
|
|
|
if (m_double_buffering_enabled)
|
|
flip(rects);
|
|
else if (created_new_backing_store)
|
|
set_current_backing_store(*m_back_store, true);
|
|
|
|
if (is_visible())
|
|
ConnectionToWindowServer::the().async_did_finish_painting(m_window_id, rects);
|
|
}
|
|
|
|
void Window::propagate_shortcuts_up_to_application(KeyEvent& event, Widget* widget)
|
|
{
|
|
VERIFY(event.type() == Event::KeyDown);
|
|
auto shortcut = Shortcut(event.modifiers(), event.key());
|
|
Action* action = nullptr;
|
|
|
|
if (widget) {
|
|
VERIFY(widget->window() == this);
|
|
|
|
do {
|
|
action = widget->action_for_shortcut(shortcut);
|
|
if (action)
|
|
break;
|
|
|
|
widget = widget->parent_widget();
|
|
} while (widget);
|
|
}
|
|
|
|
if (!action)
|
|
action = action_for_shortcut(shortcut);
|
|
if (!action)
|
|
action = Application::the()->action_for_shortcut(shortcut);
|
|
|
|
if (action) {
|
|
action->process_event(*this, event);
|
|
return;
|
|
}
|
|
|
|
event.ignore();
|
|
}
|
|
|
|
void Window::handle_key_event(KeyEvent& event)
|
|
{
|
|
if (!m_focused_widget && event.type() == Event::KeyDown && event.key() == Key_Tab && !event.ctrl() && !event.alt() && !event.super()) {
|
|
focus_a_widget_if_possible(FocusSource::Keyboard);
|
|
}
|
|
|
|
if (m_default_return_key_widget && event.key() == Key_Return)
|
|
if (!m_focused_widget || !is<Button>(m_focused_widget.ptr()))
|
|
return default_return_key_widget()->dispatch_event(event, this);
|
|
|
|
if (m_focused_widget)
|
|
m_focused_widget->dispatch_event(event, this);
|
|
else if (m_main_widget)
|
|
m_main_widget->dispatch_event(event, this);
|
|
|
|
if (event.is_accepted())
|
|
return;
|
|
|
|
if (is_blocking() || is_popup())
|
|
return;
|
|
|
|
// Only process shortcuts if this is a keydown event.
|
|
if (event.type() == Event::KeyDown)
|
|
propagate_shortcuts_up_to_application(event, nullptr);
|
|
}
|
|
|
|
void Window::handle_resize_event(ResizeEvent& event)
|
|
{
|
|
auto new_size = event.size();
|
|
|
|
// When the user is done resizing, we receive a last resize event with our actual size.
|
|
m_resizing = new_size != m_rect_when_windowless.size();
|
|
|
|
if (!m_pending_paint_event_rects.is_empty()) {
|
|
m_pending_paint_event_rects.clear_with_capacity();
|
|
m_pending_paint_event_rects.append({ {}, new_size });
|
|
}
|
|
m_rect_when_windowless.set_size(new_size);
|
|
if (m_main_widget)
|
|
m_main_widget->set_relative_rect({ {}, new_size });
|
|
}
|
|
|
|
void Window::handle_input_preemption_event(Core::Event& event)
|
|
{
|
|
if (on_input_preemption_change)
|
|
on_input_preemption_change(event.type() == Event::WindowInputPreempted);
|
|
if (!m_focused_widget)
|
|
return;
|
|
m_focused_widget->set_focus_preempted(event.type() == Event::WindowInputPreempted);
|
|
m_focused_widget->update();
|
|
}
|
|
|
|
void Window::handle_became_active_or_inactive_event(Core::Event& event)
|
|
{
|
|
if (event.type() == Event::WindowBecameActive)
|
|
Application::the()->window_did_become_active({}, *this);
|
|
else
|
|
Application::the()->window_did_become_inactive({}, *this);
|
|
if (on_active_window_change)
|
|
on_active_window_change(event.type() == Event::WindowBecameActive);
|
|
if (m_main_widget)
|
|
m_main_widget->dispatch_event(event, this);
|
|
if (m_focused_widget) {
|
|
if (event.type() == Event::WindowBecameActive)
|
|
m_focused_widget->set_focus_preempted(false);
|
|
m_focused_widget->update();
|
|
}
|
|
}
|
|
|
|
void Window::handle_close_request()
|
|
{
|
|
if (on_close_request) {
|
|
if (on_close_request() == Window::CloseRequestDecision::StayOpen)
|
|
return;
|
|
}
|
|
close();
|
|
}
|
|
|
|
void Window::handle_theme_change_event(ThemeChangeEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto dispatch_theme_change = [&](auto& widget, auto recursive) {
|
|
widget.dispatch_event(event, this);
|
|
widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
|
|
widget.dispatch_event(event, this);
|
|
recursive(widget, recursive);
|
|
return IterationDecision::Continue;
|
|
});
|
|
};
|
|
dispatch_theme_change(*m_main_widget.ptr(), dispatch_theme_change);
|
|
}
|
|
|
|
void Window::handle_fonts_change_event(FontsChangeEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto dispatch_fonts_change = [&](auto& widget, auto recursive) {
|
|
widget.dispatch_event(event, this);
|
|
widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
|
|
widget.dispatch_event(event, this);
|
|
recursive(widget, recursive);
|
|
return IterationDecision::Continue;
|
|
});
|
|
};
|
|
dispatch_fonts_change(*m_main_widget.ptr(), dispatch_fonts_change);
|
|
}
|
|
|
|
void Window::handle_screen_rects_change_event(ScreenRectsChangeEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto dispatch_screen_rects_change = [&](auto& widget, auto recursive) {
|
|
widget.dispatch_event(event, this);
|
|
widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
|
|
widget.dispatch_event(event, this);
|
|
recursive(widget, recursive);
|
|
return IterationDecision::Continue;
|
|
});
|
|
};
|
|
dispatch_screen_rects_change(*m_main_widget.ptr(), dispatch_screen_rects_change);
|
|
screen_rects_change_event(event);
|
|
}
|
|
|
|
void Window::handle_applet_area_rect_change_event(AppletAreaRectChangeEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto dispatch_applet_area_rect_change = [&](auto& widget, auto recursive) {
|
|
widget.dispatch_event(event, this);
|
|
widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
|
|
widget.dispatch_event(event, this);
|
|
recursive(widget, recursive);
|
|
return IterationDecision::Continue;
|
|
});
|
|
};
|
|
dispatch_applet_area_rect_change(*m_main_widget.ptr(), dispatch_applet_area_rect_change);
|
|
applet_area_rect_change_event(event);
|
|
}
|
|
|
|
void Window::handle_drag_move_event(DragEvent& event)
|
|
{
|
|
if (!m_main_widget)
|
|
return;
|
|
auto result = m_main_widget->hit_test(event.position());
|
|
VERIFY(result.widget);
|
|
|
|
Application::the()->set_drag_hovered_widget({}, result.widget, result.local_position, event.mime_types());
|
|
|
|
// NOTE: Setting the drag hovered widget may have executed arbitrary code, so re-check that the widget is still there.
|
|
if (!result.widget)
|
|
return;
|
|
|
|
if (result.widget->has_pending_drop()) {
|
|
DragEvent drag_move_event(static_cast<Event::Type>(event.type()), result.local_position, event.mime_types());
|
|
result.widget->dispatch_event(drag_move_event, this);
|
|
}
|
|
}
|
|
|
|
void Window::enter_event(Core::Event&)
|
|
{
|
|
}
|
|
|
|
void Window::leave_event(Core::Event&)
|
|
{
|
|
}
|
|
|
|
void Window::handle_entered_event(Core::Event& event)
|
|
{
|
|
enter_event(event);
|
|
}
|
|
|
|
void Window::handle_left_event(Core::Event& event)
|
|
{
|
|
set_hovered_widget(nullptr);
|
|
Application::the()->set_drag_hovered_widget({}, nullptr);
|
|
leave_event(event);
|
|
}
|
|
|
|
void Window::event(Core::Event& event)
|
|
{
|
|
ScopeGuard guard([&] {
|
|
// Accept the event so it doesn't bubble up to parent windows!
|
|
event.accept();
|
|
});
|
|
if (event.type() == Event::Drop)
|
|
return handle_drop_event(static_cast<DropEvent&>(event));
|
|
|
|
if (event.type() == Event::MouseUp || event.type() == Event::MouseDown || event.type() == Event::MouseDoubleClick || event.type() == Event::MouseMove || event.type() == Event::MouseWheel)
|
|
return handle_mouse_event(static_cast<MouseEvent&>(event));
|
|
|
|
if (event.type() == Event::MultiPaint)
|
|
return handle_multi_paint_event(static_cast<MultiPaintEvent&>(event));
|
|
|
|
if (event.type() == Event::KeyUp || event.type() == Event::KeyDown)
|
|
return handle_key_event(static_cast<KeyEvent&>(event));
|
|
|
|
if (event.type() == Event::WindowBecameActive || event.type() == Event::WindowBecameInactive)
|
|
return handle_became_active_or_inactive_event(event);
|
|
|
|
if (event.type() == Event::WindowInputPreempted || event.type() == Event::WindowInputRestored)
|
|
return handle_input_preemption_event(event);
|
|
|
|
if (event.type() == Event::WindowCloseRequest)
|
|
return handle_close_request();
|
|
|
|
if (event.type() == Event::WindowEntered)
|
|
return handle_entered_event(event);
|
|
|
|
if (event.type() == Event::WindowLeft)
|
|
return handle_left_event(event);
|
|
|
|
if (event.type() == Event::Resize)
|
|
return handle_resize_event(static_cast<ResizeEvent&>(event));
|
|
|
|
if (event.type() > Event::__Begin_WM_Events && event.type() < Event::__End_WM_Events)
|
|
return wm_event(static_cast<WMEvent&>(event));
|
|
|
|
if (event.type() == Event::DragMove)
|
|
return handle_drag_move_event(static_cast<DragEvent&>(event));
|
|
|
|
if (event.type() == Event::ThemeChange)
|
|
return handle_theme_change_event(static_cast<ThemeChangeEvent&>(event));
|
|
|
|
if (event.type() == Event::FontsChange)
|
|
return handle_fonts_change_event(static_cast<FontsChangeEvent&>(event));
|
|
|
|
if (event.type() == Event::ScreenRectsChange)
|
|
return handle_screen_rects_change_event(static_cast<ScreenRectsChangeEvent&>(event));
|
|
|
|
if (event.type() == Event::AppletAreaRectChange)
|
|
return handle_applet_area_rect_change_event(static_cast<AppletAreaRectChangeEvent&>(event));
|
|
|
|
Core::Object::event(event);
|
|
}
|
|
|
|
bool Window::is_visible() const
|
|
{
|
|
return m_visible;
|
|
}
|
|
|
|
void Window::update()
|
|
{
|
|
auto rect = this->rect();
|
|
update({ 0, 0, rect.width(), rect.height() });
|
|
}
|
|
|
|
void Window::force_update()
|
|
{
|
|
if (!is_visible())
|
|
return;
|
|
auto rect = this->rect();
|
|
ConnectionToWindowServer::the().async_invalidate_rect(m_window_id, { { 0, 0, rect.width(), rect.height() } }, true);
|
|
}
|
|
|
|
void Window::update(Gfx::IntRect const& a_rect)
|
|
{
|
|
if (!is_visible())
|
|
return;
|
|
|
|
for (auto& pending_rect : m_pending_paint_event_rects) {
|
|
if (pending_rect.contains(a_rect)) {
|
|
dbgln_if(UPDATE_COALESCING_DEBUG, "Ignoring {} since it's contained by pending rect {}", a_rect, pending_rect);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_pending_paint_event_rects.is_empty()) {
|
|
deferred_invoke([this] {
|
|
auto rects = move(m_pending_paint_event_rects);
|
|
if (rects.is_empty())
|
|
return;
|
|
ConnectionToWindowServer::the().async_invalidate_rect(m_window_id, rects, false);
|
|
});
|
|
}
|
|
m_pending_paint_event_rects.append(a_rect);
|
|
}
|
|
|
|
void Window::set_main_widget(Widget* widget)
|
|
{
|
|
if (m_main_widget == widget)
|
|
return;
|
|
if (m_main_widget) {
|
|
m_main_widget->set_window(nullptr);
|
|
remove_child(*m_main_widget);
|
|
}
|
|
m_main_widget = widget;
|
|
if (m_main_widget) {
|
|
add_child(*widget);
|
|
auto new_window_rect = rect();
|
|
auto new_widget_min_size = m_main_widget->effective_min_size();
|
|
new_window_rect.set_width(max(new_window_rect.width(), MUST(new_widget_min_size.width().shrink_value())));
|
|
new_window_rect.set_height(max(new_window_rect.height(), MUST(new_widget_min_size.height().shrink_value())));
|
|
set_rect(new_window_rect);
|
|
m_main_widget->set_relative_rect({ {}, new_window_rect.size() });
|
|
m_main_widget->set_window(this);
|
|
if (m_main_widget->focus_policy() != FocusPolicy::NoFocus)
|
|
m_main_widget->set_focus(true);
|
|
}
|
|
update();
|
|
}
|
|
|
|
void Window::set_default_return_key_widget(Widget* widget)
|
|
{
|
|
if (m_default_return_key_widget == widget)
|
|
return;
|
|
m_default_return_key_widget = widget;
|
|
}
|
|
|
|
void Window::set_focused_widget(Widget* widget, FocusSource source)
|
|
{
|
|
if (m_focused_widget == widget)
|
|
return;
|
|
|
|
WeakPtr<Widget> previously_focused_widget = m_focused_widget;
|
|
m_focused_widget = widget;
|
|
|
|
if (!m_focused_widget && m_previously_focused_widget)
|
|
m_focused_widget = m_previously_focused_widget;
|
|
|
|
if (m_default_return_key_widget && m_default_return_key_widget->on_focus_change)
|
|
m_default_return_key_widget->on_focus_change(m_default_return_key_widget->is_focused(), source);
|
|
|
|
if (previously_focused_widget) {
|
|
Core::EventLoop::current().post_event(*previously_focused_widget, make<FocusEvent>(Event::FocusOut, source));
|
|
previously_focused_widget->update();
|
|
if (previously_focused_widget && previously_focused_widget->on_focus_change)
|
|
previously_focused_widget->on_focus_change(previously_focused_widget->is_focused(), source);
|
|
m_previously_focused_widget = previously_focused_widget;
|
|
}
|
|
if (m_focused_widget) {
|
|
Core::EventLoop::current().post_event(*m_focused_widget, make<FocusEvent>(Event::FocusIn, source));
|
|
m_focused_widget->update();
|
|
if (m_focused_widget && m_focused_widget->on_focus_change)
|
|
m_focused_widget->on_focus_change(m_focused_widget->is_focused(), source);
|
|
}
|
|
}
|
|
|
|
void Window::set_automatic_cursor_tracking_widget(Widget* widget)
|
|
{
|
|
if (widget == m_automatic_cursor_tracking_widget)
|
|
return;
|
|
m_automatic_cursor_tracking_widget = widget;
|
|
}
|
|
|
|
void Window::set_has_alpha_channel(bool value)
|
|
{
|
|
if (m_has_alpha_channel == value)
|
|
return;
|
|
m_has_alpha_channel = value;
|
|
if (!is_visible())
|
|
return;
|
|
|
|
m_pending_paint_event_rects.clear();
|
|
m_back_store = nullptr;
|
|
m_front_store = nullptr;
|
|
|
|
ConnectionToWindowServer::the().async_set_window_has_alpha_channel(m_window_id, value);
|
|
update();
|
|
}
|
|
|
|
void Window::set_double_buffering_enabled(bool value)
|
|
{
|
|
VERIFY(!is_visible());
|
|
m_double_buffering_enabled = value;
|
|
}
|
|
|
|
void Window::set_opacity(float opacity)
|
|
{
|
|
m_opacity_when_windowless = opacity;
|
|
if (!is_visible())
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_window_opacity(m_window_id, opacity);
|
|
}
|
|
|
|
void Window::set_alpha_hit_threshold(float threshold)
|
|
{
|
|
if (threshold < 0.0f)
|
|
threshold = 0.0f;
|
|
else if (threshold > 1.0f)
|
|
threshold = 1.0f;
|
|
if (m_alpha_hit_threshold == threshold)
|
|
return;
|
|
m_alpha_hit_threshold = threshold;
|
|
if (!is_visible())
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_window_alpha_hit_threshold(m_window_id, threshold);
|
|
}
|
|
|
|
void Window::set_hovered_widget(Widget* widget)
|
|
{
|
|
if (widget == m_hovered_widget)
|
|
return;
|
|
|
|
if (m_hovered_widget)
|
|
Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Leave));
|
|
|
|
m_hovered_widget = widget;
|
|
|
|
if (m_hovered_widget)
|
|
Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Enter));
|
|
|
|
auto* app = Application::the();
|
|
if (app && app->hover_debugging_enabled())
|
|
update();
|
|
}
|
|
|
|
void Window::set_current_backing_store(WindowBackingStore& backing_store, bool flush_immediately) const
|
|
{
|
|
auto& bitmap = backing_store.bitmap();
|
|
ConnectionToWindowServer::the().set_window_backing_store(
|
|
m_window_id,
|
|
32,
|
|
bitmap.pitch(),
|
|
bitmap.anonymous_buffer().fd(),
|
|
backing_store.serial(),
|
|
bitmap.has_alpha_channel(),
|
|
bitmap.size(),
|
|
backing_store.visible_size(),
|
|
flush_immediately);
|
|
}
|
|
|
|
void Window::flip(Vector<Gfx::IntRect, 32> const& dirty_rects)
|
|
{
|
|
swap(m_front_store, m_back_store);
|
|
|
|
set_current_backing_store(*m_front_store);
|
|
|
|
if (!m_back_store || m_back_store->size() != m_front_store->size()) {
|
|
m_back_store = create_backing_store(m_front_store->size()).release_value_but_fixme_should_propagate_errors();
|
|
memcpy(m_back_store->bitmap().scanline(0), m_front_store->bitmap().scanline(0), m_front_store->bitmap().size_in_bytes());
|
|
m_back_store->bitmap().set_volatile();
|
|
return;
|
|
}
|
|
|
|
// Copy whatever was painted from the front to the back.
|
|
Painter painter(m_back_store->bitmap());
|
|
for (auto& dirty_rect : dirty_rects)
|
|
painter.blit(dirty_rect.location(), m_front_store->bitmap(), dirty_rect, 1.0f, false);
|
|
|
|
m_back_store->bitmap().set_volatile();
|
|
}
|
|
|
|
ErrorOr<NonnullOwnPtr<WindowBackingStore>> Window::create_backing_store(Gfx::IntSize size)
|
|
{
|
|
auto format = m_has_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
|
|
|
|
VERIFY(!size.is_empty());
|
|
size_t pitch = Gfx::Bitmap::minimum_pitch(size.width(), format);
|
|
size_t size_in_bytes = size.height() * pitch;
|
|
|
|
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(size_in_bytes, PAGE_SIZE)));
|
|
|
|
// FIXME: Plumb scale factor here eventually.
|
|
auto bitmap = TRY(Gfx::Bitmap::try_create_with_anonymous_buffer(format, buffer, size, 1, {}));
|
|
return make<WindowBackingStore>(bitmap);
|
|
}
|
|
|
|
void Window::wm_event(WMEvent&)
|
|
{
|
|
}
|
|
|
|
void Window::screen_rects_change_event(ScreenRectsChangeEvent&)
|
|
{
|
|
}
|
|
|
|
void Window::applet_area_rect_change_event(AppletAreaRectChangeEvent&)
|
|
{
|
|
}
|
|
|
|
void Window::set_icon(Gfx::Bitmap const* icon)
|
|
{
|
|
if (m_icon == icon)
|
|
return;
|
|
|
|
Gfx::IntSize icon_size = icon ? icon->size() : Gfx::IntSize(16, 16);
|
|
|
|
m_icon = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, icon_size).release_value_but_fixme_should_propagate_errors();
|
|
if (icon) {
|
|
Painter painter(*m_icon);
|
|
painter.blit({ 0, 0 }, *icon, icon->rect());
|
|
}
|
|
|
|
apply_icon();
|
|
}
|
|
|
|
void Window::apply_icon()
|
|
{
|
|
if (!m_icon)
|
|
return;
|
|
|
|
if (!is_visible())
|
|
return;
|
|
|
|
ConnectionToWindowServer::the().async_set_window_icon_bitmap(m_window_id, m_icon->to_shareable_bitmap());
|
|
}
|
|
|
|
void Window::start_interactive_resize(ResizeDirection resize_direction)
|
|
{
|
|
ConnectionToWindowServer::the().async_start_window_resize(m_window_id, (i32)resize_direction);
|
|
}
|
|
|
|
Vector<Widget&> Window::focusable_widgets(FocusSource source) const
|
|
{
|
|
if (!m_main_widget)
|
|
return {};
|
|
|
|
HashTable<Widget*> seen_widgets;
|
|
Vector<Widget&> collected_widgets;
|
|
|
|
Function<void(Widget&)> collect_focusable_widgets = [&](auto& widget) {
|
|
bool widget_accepts_focus = false;
|
|
switch (source) {
|
|
case FocusSource::Keyboard:
|
|
widget_accepts_focus = has_flag(widget.focus_policy(), FocusPolicy::TabFocus);
|
|
break;
|
|
case FocusSource::Mouse:
|
|
widget_accepts_focus = has_flag(widget.focus_policy(), FocusPolicy::ClickFocus);
|
|
break;
|
|
case FocusSource::Programmatic:
|
|
widget_accepts_focus = widget.focus_policy() != FocusPolicy::NoFocus;
|
|
break;
|
|
}
|
|
|
|
if (widget_accepts_focus) {
|
|
auto& effective_focus_widget = widget.focus_proxy() ? *widget.focus_proxy() : widget;
|
|
if (seen_widgets.set(&effective_focus_widget) == AK::HashSetResult::InsertedNewEntry)
|
|
collected_widgets.append(effective_focus_widget);
|
|
}
|
|
widget.for_each_child_widget([&](auto& child) {
|
|
if (!child.is_visible())
|
|
return IterationDecision::Continue;
|
|
if (!child.is_enabled())
|
|
return IterationDecision::Continue;
|
|
if (!child.is_auto_focusable())
|
|
return IterationDecision::Continue;
|
|
collect_focusable_widgets(child);
|
|
return IterationDecision::Continue;
|
|
});
|
|
};
|
|
|
|
collect_focusable_widgets(const_cast<Widget&>(*m_main_widget));
|
|
return collected_widgets;
|
|
}
|
|
|
|
void Window::set_fullscreen(bool fullscreen)
|
|
{
|
|
if (m_fullscreen == fullscreen)
|
|
return;
|
|
m_fullscreen = fullscreen;
|
|
if (!is_visible())
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_fullscreen(m_window_id, fullscreen);
|
|
}
|
|
|
|
void Window::set_frameless(bool frameless)
|
|
{
|
|
if (m_frameless == frameless)
|
|
return;
|
|
m_frameless = frameless;
|
|
if (!is_visible())
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_frameless(m_window_id, frameless);
|
|
|
|
if (!frameless)
|
|
apply_icon();
|
|
}
|
|
|
|
void Window::set_forced_shadow(bool shadow)
|
|
{
|
|
if (m_forced_shadow == shadow)
|
|
return;
|
|
m_forced_shadow = shadow;
|
|
if (!is_visible())
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_forced_shadow(m_window_id, shadow);
|
|
}
|
|
|
|
void Window::set_obey_widget_min_size(bool obey_widget_min_size)
|
|
{
|
|
if (m_obey_widget_min_size != obey_widget_min_size) {
|
|
m_obey_widget_min_size = obey_widget_min_size;
|
|
schedule_relayout();
|
|
}
|
|
}
|
|
|
|
void Window::set_maximized(bool maximized)
|
|
{
|
|
m_maximized = maximized;
|
|
if (!is_visible())
|
|
return;
|
|
|
|
ConnectionToWindowServer::the().async_set_maximized(m_window_id, maximized);
|
|
}
|
|
|
|
void Window::set_minimized(bool minimized)
|
|
{
|
|
if (!is_minimizable())
|
|
return;
|
|
|
|
m_minimized = minimized;
|
|
if (!is_visible())
|
|
return;
|
|
|
|
ConnectionToWindowServer::the().async_set_minimized(m_window_id, minimized);
|
|
}
|
|
|
|
void Window::update_min_size()
|
|
{
|
|
if (main_widget()) {
|
|
main_widget()->do_layout();
|
|
if (m_obey_widget_min_size) {
|
|
auto min_size = main_widget()->effective_min_size();
|
|
Gfx::IntSize size = { MUST(min_size.width().shrink_value()), MUST(min_size.height().shrink_value()) };
|
|
m_minimum_size_when_windowless = size;
|
|
if (is_visible())
|
|
ConnectionToWindowServer::the().async_set_window_minimum_size(m_window_id, size);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Window::schedule_relayout()
|
|
{
|
|
if (m_layout_pending || !is_visible())
|
|
return;
|
|
m_layout_pending = true;
|
|
deferred_invoke([this] {
|
|
update_min_size();
|
|
update();
|
|
m_layout_pending = false;
|
|
});
|
|
}
|
|
|
|
void Window::refresh_system_theme()
|
|
{
|
|
ConnectionToWindowServer::the().async_refresh_system_theme();
|
|
}
|
|
|
|
void Window::for_each_window(Badge<ConnectionToWindowServer>, Function<void(Window&)> callback)
|
|
{
|
|
for (auto& e : *reified_windows) {
|
|
VERIFY(e.value);
|
|
callback(*e.value);
|
|
}
|
|
}
|
|
|
|
void Window::update_all_windows(Badge<ConnectionToWindowServer>)
|
|
{
|
|
for (auto& e : *reified_windows) {
|
|
e.value->force_update();
|
|
}
|
|
}
|
|
|
|
void Window::notify_state_changed(Badge<ConnectionToWindowServer>, bool minimized, bool maximized, bool occluded)
|
|
{
|
|
m_visible_for_timer_purposes = !minimized && !occluded;
|
|
|
|
m_maximized = maximized;
|
|
|
|
// When double buffering is enabled, minimization/occlusion means we can mark the front bitmap volatile (in addition to the back bitmap.)
|
|
// When double buffering is disabled, there is only the back bitmap (which we can now mark volatile!)
|
|
auto& store = m_double_buffering_enabled ? m_front_store : m_back_store;
|
|
if (!store)
|
|
return;
|
|
if (minimized || occluded) {
|
|
store->bitmap().set_volatile();
|
|
} else {
|
|
bool was_purged = false;
|
|
bool bitmap_has_memory = store->bitmap().set_nonvolatile(was_purged);
|
|
if (!bitmap_has_memory) {
|
|
// Not enough memory to make the bitmap non-volatile. Lose the bitmap and schedule an update.
|
|
// Let the paint system figure out what to do.
|
|
store = nullptr;
|
|
update();
|
|
} else if (was_purged) {
|
|
// The bitmap memory was purged by the kernel, but we have all-new zero-filled pages.
|
|
// Schedule an update to regenerate the bitmap.
|
|
update();
|
|
}
|
|
}
|
|
}
|
|
|
|
Action* Window::action_for_shortcut(Shortcut const& shortcut)
|
|
{
|
|
return Action::find_action_for_shortcut(*this, shortcut);
|
|
}
|
|
|
|
void Window::set_base_size(Gfx::IntSize base_size)
|
|
{
|
|
if (m_base_size == base_size)
|
|
return;
|
|
m_base_size = base_size;
|
|
if (is_visible())
|
|
ConnectionToWindowServer::the().async_set_window_base_size_and_size_increment(m_window_id, m_base_size, m_size_increment);
|
|
}
|
|
|
|
void Window::set_size_increment(Gfx::IntSize size_increment)
|
|
{
|
|
if (m_size_increment == size_increment)
|
|
return;
|
|
m_size_increment = size_increment;
|
|
if (is_visible())
|
|
ConnectionToWindowServer::the().async_set_window_base_size_and_size_increment(m_window_id, m_base_size, m_size_increment);
|
|
}
|
|
|
|
void Window::set_resize_aspect_ratio(Optional<Gfx::IntSize> const& ratio)
|
|
{
|
|
if (m_resize_aspect_ratio == ratio)
|
|
return;
|
|
|
|
m_resize_aspect_ratio = ratio;
|
|
if (is_visible())
|
|
ConnectionToWindowServer::the().async_set_window_resize_aspect_ratio(m_window_id, m_resize_aspect_ratio);
|
|
}
|
|
|
|
void Window::did_add_widget(Badge<Widget>, Widget&)
|
|
{
|
|
if (!m_focused_widget)
|
|
focus_a_widget_if_possible(FocusSource::Mouse);
|
|
}
|
|
|
|
void Window::did_remove_widget(Badge<Widget>, Widget& widget)
|
|
{
|
|
if (m_focused_widget == &widget)
|
|
m_focused_widget = nullptr;
|
|
if (m_hovered_widget == &widget)
|
|
m_hovered_widget = nullptr;
|
|
if (m_automatic_cursor_tracking_widget == &widget)
|
|
m_automatic_cursor_tracking_widget = nullptr;
|
|
}
|
|
|
|
void Window::set_progress(Optional<int> progress)
|
|
{
|
|
VERIFY(m_window_id);
|
|
ConnectionToWindowServer::the().async_set_window_progress(m_window_id, progress);
|
|
}
|
|
|
|
void Window::update_cursor()
|
|
{
|
|
auto new_cursor = m_cursor;
|
|
|
|
auto is_usable_cursor = [](auto& cursor) {
|
|
return cursor.template has<NonnullRefPtr<Gfx::Bitmap>>() || cursor.template get<Gfx::StandardCursor>() != Gfx::StandardCursor::None;
|
|
};
|
|
|
|
// NOTE: If there's an automatic cursor tracking widget, we retain its cursor until tracking stops.
|
|
if (auto widget = m_automatic_cursor_tracking_widget) {
|
|
if (is_usable_cursor(widget->override_cursor()))
|
|
new_cursor = widget->override_cursor();
|
|
} else if (auto widget = m_hovered_widget) {
|
|
if (is_usable_cursor(widget->override_cursor()))
|
|
new_cursor = widget->override_cursor();
|
|
}
|
|
|
|
if (are_cursors_the_same(m_effective_cursor, new_cursor))
|
|
return;
|
|
m_effective_cursor = new_cursor;
|
|
|
|
if (new_cursor.has<NonnullRefPtr<Gfx::Bitmap>>())
|
|
ConnectionToWindowServer::the().async_set_window_custom_cursor(m_window_id, new_cursor.get<NonnullRefPtr<Gfx::Bitmap>>()->to_shareable_bitmap());
|
|
else
|
|
ConnectionToWindowServer::the().async_set_window_cursor(m_window_id, (u32)new_cursor.get<Gfx::StandardCursor>());
|
|
}
|
|
|
|
void Window::focus_a_widget_if_possible(FocusSource source)
|
|
{
|
|
auto focusable_widgets = this->focusable_widgets(source);
|
|
if (!focusable_widgets.is_empty())
|
|
set_focused_widget(&focusable_widgets[0], source);
|
|
}
|
|
|
|
void Window::did_disable_focused_widget(Badge<Widget>)
|
|
{
|
|
focus_a_widget_if_possible(FocusSource::Mouse);
|
|
}
|
|
|
|
bool Window::is_active() const
|
|
{
|
|
VERIFY(Application::the());
|
|
return this == Application::the()->active_window();
|
|
}
|
|
|
|
Gfx::Bitmap* Window::back_bitmap()
|
|
{
|
|
return m_back_store ? &m_back_store->bitmap() : nullptr;
|
|
}
|
|
|
|
ErrorOr<void> Window::try_add_menu(NonnullRefPtr<Menu> menu)
|
|
{
|
|
TRY(m_menubar->try_add_menu({}, move(menu)));
|
|
if (m_window_id) {
|
|
menu->realize_menu_if_needed();
|
|
ConnectionToWindowServer::the().async_add_menu(m_window_id, menu->menu_id());
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Menu>> Window::try_add_menu(DeprecatedString name)
|
|
{
|
|
auto menu = TRY(m_menubar->try_add_menu({}, move(name)));
|
|
if (m_window_id) {
|
|
menu->realize_menu_if_needed();
|
|
ConnectionToWindowServer::the().async_add_menu(m_window_id, menu->menu_id());
|
|
}
|
|
return menu;
|
|
}
|
|
|
|
Menu& Window::add_menu(DeprecatedString name)
|
|
{
|
|
auto menu = MUST(try_add_menu(move(name)));
|
|
return *menu;
|
|
}
|
|
|
|
void Window::flash_menubar_menu_for(MenuItem const& menu_item)
|
|
{
|
|
if (!Desktop::the().system_effects().flash_menus())
|
|
return;
|
|
auto menu_id = menu_item.menu_id();
|
|
if (menu_id < 0)
|
|
return;
|
|
|
|
ConnectionToWindowServer::the().async_flash_menubar_menu(m_window_id, menu_id);
|
|
}
|
|
|
|
bool Window::is_modified() const
|
|
{
|
|
if (!m_window_id)
|
|
return false;
|
|
return ConnectionToWindowServer::the().is_window_modified(m_window_id);
|
|
}
|
|
|
|
void Window::set_modified(bool modified)
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
ConnectionToWindowServer::the().async_set_window_modified(m_window_id, modified);
|
|
}
|
|
|
|
void Window::flush_pending_paints_immediately()
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
if (m_pending_paint_event_rects.is_empty())
|
|
return;
|
|
MultiPaintEvent paint_event(move(m_pending_paint_event_rects), size());
|
|
handle_multi_paint_event(paint_event);
|
|
}
|
|
|
|
void Window::set_always_on_top(bool always_on_top)
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
ConnectionToWindowServer::the().set_always_on_top(m_window_id, always_on_top);
|
|
}
|
|
|
|
}
|