ladybird/LibGUI/GWindow.cpp
Andreas Kling 6a5d92f0ad WindowServer+LibGUI: Allow changing whether windows have alpha channels.
Use this in Terminal to tell the window server to not bother with the alpha
channel in the backing store if we're running without transparency.
Semi-transparent terminals look neat but they slow everything down, so this
keeps things fast while making it easy to switch to the flashy mode. :^)
2019-05-03 21:07:16 +02:00

516 lines
18 KiB
C++

#include "GWindow.h"
#include "GEvent.h"
#include "GEventLoop.h"
#include "GWidget.h"
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibGUI/GPainter.h>
#include <LibC/stdio.h>
#include <LibC/stdlib.h>
#include <LibC/unistd.h>
#include <AK/HashMap.h>
//#define UPDATE_COALESCING_DEBUG
static HashMap<int, GWindow*>* s_windows;
static HashMap<int, GWindow*>& windows()
{
if (!s_windows)
s_windows = new HashMap<int, GWindow*>;
return *s_windows;
}
GWindow* GWindow::from_window_id(int window_id)
{
auto it = windows().find(window_id);
if (it != windows().end())
return (*it).value;
return nullptr;
}
GWindow::GWindow(CObject* parent)
: CObject(parent)
{
m_rect_when_windowless = { 100, 400, 140, 140 };
m_title_when_windowless = "GWindow";
}
GWindow::~GWindow()
{
if (m_main_widget)
delete m_main_widget;
hide();
}
void GWindow::close()
{
if (should_exit_event_loop_on_close())
GEventLoop::current().quit(0);
delete_later();
}
void GWindow::show()
{
if (m_window_id)
return;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::CreateWindow;
request.window_id = m_window_id;
request.window.rect = m_rect_when_windowless;
request.window.has_alpha_channel = m_has_alpha_channel;
request.window.modal = m_modal;
request.window.resizable = m_resizable;
request.window.opacity = m_opacity_when_windowless;
request.window.background_color = m_background_color.value();
request.window.size_increment = m_size_increment;
request.window.base_size = m_base_size;
request.window.type = (WSAPI_WindowType)m_window_type;
ASSERT(m_title_when_windowless.length() < (ssize_t)sizeof(request.text));
strcpy(request.text, m_title_when_windowless.characters());
request.text_length = m_title_when_windowless.length();
auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidCreateWindow);
m_window_id = response.window_id;
windows().set(m_window_id, this);
update();
}
void GWindow::hide()
{
if (!m_window_id)
return;
windows().remove(m_window_id);
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::DestroyWindow;
request.window_id = m_window_id;
GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyWindow);
m_window_id = 0;
m_pending_paint_event_rects.clear();
m_back_bitmap = nullptr;
m_front_bitmap = nullptr;
}
void GWindow::set_title(const String& title)
{
m_title_when_windowless = title;
if (!m_window_id)
return;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::SetWindowTitle;
request.window_id = m_window_id;
ASSERT(m_title_when_windowless.length() < (ssize_t)sizeof(request.text));
strcpy(request.text, m_title_when_windowless.characters());
request.text_length = m_title_when_windowless.length();
GEventLoop::current().post_message_to_server(request);
}
String GWindow::title() const
{
if (!m_window_id)
return m_title_when_windowless;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::GetWindowTitle;
request.window_id = m_window_id;
auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidGetWindowTitle);
return String(response.text, response.text_length);
}
Rect GWindow::rect() const
{
if (!m_window_id)
return m_rect_when_windowless;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::GetWindowRect;
request.window_id = m_window_id;
auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidGetWindowRect);
ASSERT(response.window_id == m_window_id);
return response.window.rect;
}
void GWindow::set_rect(const Rect& a_rect)
{
m_rect_when_windowless = a_rect;
if (!m_window_id) {
if (m_main_widget)
m_main_widget->resize(m_rect_when_windowless.size());
return;
}
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::SetWindowRect;
request.window_id = m_window_id;
request.window.rect = a_rect;
GEventLoop::current().post_message_to_server(request);
if (m_back_bitmap && m_back_bitmap->size() != a_rect.size())
m_back_bitmap = nullptr;
if (m_front_bitmap && m_front_bitmap->size() != a_rect.size())
m_front_bitmap = nullptr;
if (m_main_widget)
m_main_widget->resize(a_rect.size());
}
void GWindow::set_window_type(GWindowType window_type)
{
m_window_type = window_type;
}
void GWindow::set_override_cursor(GStandardCursor cursor)
{
if (!m_window_id)
return;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::SetWindowOverrideCursor;
request.window_id = m_window_id;
request.cursor.cursor = (WSAPI_StandardCursor)cursor;
GEventLoop::current().post_message_to_server(request);
}
void GWindow::event(CEvent& event)
{
if (event.type() == GEvent::MouseUp || event.type() == GEvent::MouseDown || event.type() == GEvent::MouseMove) {
auto& mouse_event = static_cast<GMouseEvent&>(event);
if (m_global_cursor_tracking_widget) {
auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect();
Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
auto local_event = make<GMouseEvent>((GEvent::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers());
m_global_cursor_tracking_widget->event(*local_event);
return;
}
if (m_automatic_cursor_tracking_widget) {
auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect();
Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
auto local_event = make<GMouseEvent>((GEvent::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers());
m_automatic_cursor_tracking_widget->event(*local_event);
if (mouse_event.buttons() == 0)
m_automatic_cursor_tracking_widget = nullptr;
return;
}
if (!m_main_widget)
return;
if (m_main_widget) {
auto result = m_main_widget->hit_test(mouse_event.position());
auto local_event = make<GMouseEvent>((GEvent::Type)event.type(), result.local_position, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers());
ASSERT(result.widget);
set_hovered_widget(result.widget);
if (mouse_event.buttons() != 0 && !m_automatic_cursor_tracking_widget)
m_automatic_cursor_tracking_widget = result.widget->make_weak_ptr();
if (result.widget != m_global_cursor_tracking_widget.ptr())
return result.widget->event(*local_event);
}
return;
}
if (event.type() == GEvent::MultiPaint) {
if (!m_window_id)
return;
if (!m_main_widget)
return;
auto& paint_event = static_cast<GMultiPaintEvent&>(event);
auto rects = paint_event.rects();
ASSERT(!rects.is_empty());
if (m_back_bitmap && m_back_bitmap->size() != paint_event.window_size()) {
// Eagerly discard the backing store if we learn from this paint event that it needs to be bigger.
// Otherwise we would have to wait for a resize event to tell us. This way we don't waste the
// effort on painting into an undersized bitmap that will be thrown away anyway.
m_back_bitmap = nullptr;
}
bool created_new_backing_store = !m_back_bitmap;
if (!m_back_bitmap)
m_back_bitmap = create_backing_bitmap(paint_event.window_size());
auto rect = rects.first();
if (rect.is_empty() || created_new_backing_store) {
rects.clear();
rects.append({ { }, paint_event.window_size() });
}
for (auto& rect : rects) {
m_main_widget->event(*make<GPaintEvent>(rect));
if (m_double_buffering_enabled)
flip(rect);
}
if (!m_double_buffering_enabled && created_new_backing_store)
set_current_backing_bitmap(*m_back_bitmap, true);
if (m_window_id) {
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::DidFinishPainting;
message.window_id = m_window_id;
message.rect_count = rects.size();
for (int i = 0; i < min(WSAPI_ClientMessage::max_inline_rect_count, rects.size()); ++i)
message.rects[i] = rects[i];
ByteBuffer extra_data;
if (rects.size() > WSAPI_ClientMessage::max_inline_rect_count)
extra_data = ByteBuffer::wrap(&rects[WSAPI_ClientMessage::max_inline_rect_count], (rects.size() - WSAPI_ClientMessage::max_inline_rect_count) * sizeof(WSAPI_Rect));
GEventLoop::current().post_message_to_server(message, extra_data);
}
return;
}
if (event.type() == GEvent::KeyUp || event.type() == GEvent::KeyDown) {
if (m_focused_widget)
return m_focused_widget->event(event);
if (m_main_widget)
return m_main_widget->event(event);
return;
}
if (event.type() == GEvent::WindowBecameActive || event.type() == GEvent::WindowBecameInactive) {
m_is_active = event.type() == GEvent::WindowBecameActive;
if (m_main_widget)
m_main_widget->event(event);
if (m_focused_widget)
m_focused_widget->update();
return;
}
if (event.type() == GEvent::WindowCloseRequest) {
close();
return;
}
if (event.type() == GEvent::WindowLeft) {
set_hovered_widget(nullptr);
return;
}
if (event.type() == GEvent::Resize) {
auto new_size = static_cast<GResizeEvent&>(event).size();
if (m_back_bitmap && m_back_bitmap->size() != new_size)
m_back_bitmap = nullptr;
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 = { { }, new_size };
m_main_widget->set_relative_rect({ { }, new_size });
return;
}
if (event.type() > GEvent::__Begin_WM_Events && event.type() < GEvent::__End_WM_Events)
return wm_event(static_cast<GWMEvent&>(event));
CObject::event(event);
}
bool GWindow::is_visible() const
{
return false;
}
void GWindow::update(const Rect& a_rect)
{
if (!m_window_id)
return;
for (auto& pending_rect : m_pending_paint_event_rects) {
if (pending_rect.contains(a_rect)) {
#ifdef UPDATE_COALESCING_DEBUG
dbgprintf("Ignoring %s since it's contained by pending rect %s\n", a_rect.to_string().characters(), pending_rect.to_string().characters());
#endif
return;
}
}
if (m_pending_paint_event_rects.is_empty()) {
deferred_invoke([this] (auto&) {
auto rects = move(m_pending_paint_event_rects);
if (rects.is_empty())
return;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::InvalidateRect;
request.window_id = m_window_id;
for (int i = 0; i < min(WSAPI_ClientMessage::max_inline_rect_count, rects.size()); ++i)
request.rects[i] = rects[i];
ByteBuffer extra_data;
if (rects.size() > WSAPI_ClientMessage::max_inline_rect_count)
extra_data = ByteBuffer::wrap(&rects[WSAPI_ClientMessage::max_inline_rect_count], (rects.size() - WSAPI_ClientMessage::max_inline_rect_count) * sizeof(WSAPI_Rect));
request.rect_count = rects.size();
GEventLoop::current().post_message_to_server(request, extra_data);
});
}
m_pending_paint_event_rects.append(a_rect);
}
void GWindow::set_main_widget(GWidget* widget)
{
if (m_main_widget == widget)
return;
m_main_widget = widget;
if (m_main_widget) {
auto new_window_rect = rect();
if (m_main_widget->horizontal_size_policy() == SizePolicy::Fixed)
new_window_rect.set_width(m_main_widget->preferred_size().width());
if (m_main_widget->vertical_size_policy() == SizePolicy::Fixed)
new_window_rect.set_height(m_main_widget->preferred_size().height());
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->accepts_focus())
m_main_widget->set_focus(true);
}
update();
}
void GWindow::set_focused_widget(GWidget* widget)
{
if (m_focused_widget == widget)
return;
if (m_focused_widget) {
GEventLoop::current().post_event(*m_focused_widget, make<GEvent>(GEvent::FocusOut));
m_focused_widget->update();
}
m_focused_widget = widget ? widget->make_weak_ptr() : nullptr;
if (m_focused_widget) {
GEventLoop::current().post_event(*m_focused_widget, make<GEvent>(GEvent::FocusIn));
m_focused_widget->update();
}
}
void GWindow::set_global_cursor_tracking_widget(GWidget* widget)
{
if (widget == m_global_cursor_tracking_widget)
return;
m_global_cursor_tracking_widget = widget ? widget->make_weak_ptr() : nullptr;
}
void GWindow::set_automatic_cursor_tracking_widget(GWidget* widget)
{
if (widget == m_automatic_cursor_tracking_widget)
return;
m_automatic_cursor_tracking_widget = widget ? widget->make_weak_ptr() : nullptr;
}
void GWindow::set_has_alpha_channel(bool value)
{
if (m_has_alpha_channel == value)
return;
m_has_alpha_channel = value;
if (!m_window_id)
return;
m_pending_paint_event_rects.clear();
m_back_bitmap = nullptr;
m_front_bitmap = nullptr;
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::SetWindowHasAlphaChannel;
message.window_id = m_window_id;
message.value = value;
GEventLoop::current().sync_request(message, WSAPI_ServerMessage::DidSetWindowHasAlphaChannel);
update();
}
void GWindow::set_double_buffering_enabled(bool value)
{
ASSERT(!m_window_id);
m_double_buffering_enabled = value;
}
void GWindow::set_opacity(float opacity)
{
m_opacity_when_windowless = opacity;
if (!m_window_id)
return;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::SetWindowOpacity;
request.window_id = m_window_id;
request.window.opacity = opacity;
m_opacity_when_windowless = opacity;
GEventLoop::current().post_message_to_server(request);
}
void GWindow::set_hovered_widget(GWidget* widget)
{
if (widget == m_hovered_widget)
return;
if (m_hovered_widget)
GEventLoop::current().post_event(*m_hovered_widget, make<GEvent>(GEvent::Leave));
m_hovered_widget = widget ? widget->make_weak_ptr() : nullptr;
if (m_hovered_widget)
GEventLoop::current().post_event(*m_hovered_widget, make<GEvent>(GEvent::Enter));
}
void GWindow::set_current_backing_bitmap(GraphicsBitmap& bitmap, bool flush_immediately)
{
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::SetWindowBackingStore;
message.window_id = m_window_id;
message.backing.bpp = 32;
message.backing.pitch = bitmap.pitch();
message.backing.shared_buffer_id = bitmap.shared_buffer_id();
message.backing.has_alpha_channel = bitmap.has_alpha_channel();
message.backing.size = bitmap.size();
message.backing.flush_immediately = flush_immediately;
GEventLoop::current().sync_request(message, WSAPI_ServerMessage::Type::DidSetWindowBackingStore);
}
void GWindow::flip(const Rect& dirty_rect)
{
swap(m_front_bitmap, m_back_bitmap);
set_current_backing_bitmap(*m_front_bitmap);
if (!m_back_bitmap || m_back_bitmap->size() != m_front_bitmap->size()) {
m_back_bitmap = create_backing_bitmap(m_front_bitmap->size());
memcpy(m_back_bitmap->scanline(0), m_front_bitmap->scanline(0), m_front_bitmap->size().area() * sizeof(RGBA32));
return;
}
// Copy whatever was painted from the front to the back.
Painter painter(*m_back_bitmap);
painter.blit(dirty_rect.location(), *m_front_bitmap, dirty_rect);
}
Retained<GraphicsBitmap> GWindow::create_backing_bitmap(const Size& size)
{
ASSERT(GEventLoop::server_pid());
ASSERT(!size.is_empty());
size_t size_in_bytes = size.area() * sizeof(RGBA32);
auto shared_buffer = SharedBuffer::create(GEventLoop::server_pid(), size_in_bytes);
ASSERT(shared_buffer);
auto format = m_has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32;
return GraphicsBitmap::create_with_shared_buffer(format, *shared_buffer, size);
}
void GWindow::set_modal(bool modal)
{
ASSERT(!m_window_id);
m_modal = modal;
}
void GWindow::wm_event(GWMEvent&)
{
}
void GWindow::set_icon_path(const String& path)
{
if (m_icon_path == path)
return;
m_icon_path = path;
if (!m_window_id)
return;
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::SetWindowIcon;
message.window_id = m_window_id;
ASSERT(path.length() < sizeof(message.text));
strcpy(message.text, path.characters());
message.text_length = path.length();
GEventLoop::post_message_to_server(message);
}
void GWindow::start_wm_resize()
{
WSAPI_ClientMessage message;
message.type = WSAPI_ClientMessage::Type::WM_StartWindowResize;
message.wm.client_id = GEventLoop::my_client_id();
message.wm.window_id = m_window_id;
GEventLoop::post_message_to_server(message);
}