summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-02-05 10:31:37 +0100
committerAndreas Kling <awesomekling@gmail.com>2019-02-05 10:31:37 +0100
commit11db8c1697533616020b5105afe490b99fd6d440 (patch)
tree29897b22c079c7ef7b09ab54726c56eb87e734f4
parentd0078b6574d7022f207f3af492d2d8ec142b9bd1 (diff)
downloadserenity-11db8c1697533616020b5105afe490b99fd6d440.zip
Add a simple close button ("X") to windows.
Clicking the button generates a WindowCloseRequest event which the client app then has to deal with. The default behavior for GWindow is to close() itself. I also added a flag, GWindow::should_exit_event_loop_on_close() which does what it sounds like it does. This patch exposed some bugs in GWindow and GWidget teardown.
-rw-r--r--Clock/main.cpp3
-rw-r--r--Kernel/GUITypes.h1
-rw-r--r--LibGUI/GEvent.h1
-rw-r--r--LibGUI/GEventLoop.cpp17
-rw-r--r--LibGUI/GEventLoop.h5
-rw-r--r--LibGUI/GObject.cpp1
-rw-r--r--LibGUI/GWindow.cpp11
-rw-r--r--LibGUI/GWindow.h4
-rw-r--r--Terminal/main.cpp2
-rw-r--r--Userland/guitest2.cpp1
-rw-r--r--WindowServer/WSMessage.h1
-rw-r--r--WindowServer/WSWindow.cpp3
-rw-r--r--WindowServer/WSWindowManager.cpp53
-rw-r--r--WindowServer/WSWindowManager.h1
14 files changed, 103 insertions, 1 deletions
diff --git a/Clock/main.cpp b/Clock/main.cpp
index 483d5cc88f..98593d1905 100644
--- a/Clock/main.cpp
+++ b/Clock/main.cpp
@@ -8,7 +8,8 @@ int main(int, char**)
auto* window = new GWindow;
window->set_title("Clock");
- window->set_rect({ 100, 100, 100, 40 });
+ window->set_rect({ 600, 100, 100, 40 });
+ window->set_should_exit_app_on_close(true);
auto* clock_widget = new ClockWidget;
clock_widget->set_relative_rect({ 0, 0, 100, 40 });
diff --git a/Kernel/GUITypes.h b/Kernel/GUITypes.h
index 9059ed96e4..61aa6587eb 100644
--- a/Kernel/GUITypes.h
+++ b/Kernel/GUITypes.h
@@ -66,6 +66,7 @@ struct GUI_Event {
KeyUp,
WindowActivated,
WindowDeactivated,
+ WindowCloseRequest,
};
Type type { Invalid };
int window_id { -1 };
diff --git a/LibGUI/GEvent.h b/LibGUI/GEvent.h
index 2e35aa4de8..52846cb0ae 100644
--- a/LibGUI/GEvent.h
+++ b/LibGUI/GEvent.h
@@ -24,6 +24,7 @@ public:
WindowBecameActive,
FocusIn,
FocusOut,
+ WindowCloseRequest,
};
GEvent() { }
diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp
index fa42115cf8..187f1df816 100644
--- a/LibGUI/GEventLoop.cpp
+++ b/LibGUI/GEventLoop.cpp
@@ -35,6 +35,12 @@ GEventLoop& GEventLoop::main()
return *s_mainGEventLoop;
}
+void GEventLoop::exit(int code)
+{
+ m_exit_requested = true;
+ m_exit_code = code;
+}
+
int GEventLoop::exec()
{
m_event_fd = open("/dev/gui_events", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
@@ -45,6 +51,8 @@ int GEventLoop::exec()
m_running = true;
for (;;) {
+ if (m_exit_requested)
+ return m_exit_code;
if (m_queued_events.is_empty())
wait_for_event();
Vector<QueuedEvent> events = move(m_queued_events);
@@ -69,6 +77,7 @@ int GEventLoop::exec()
}
}
}
+ ASSERT_NOT_REACHED();
}
void GEventLoop::post_event(GObject* receiver, OwnPtr<GEvent>&& event)
@@ -95,6 +104,11 @@ void GEventLoop::handle_window_activation_event(const GUI_Event& event, GWindow&
post_event(&window, make<GEvent>(event.type == GUI_Event::Type::WindowActivated ? GEvent::WindowBecameActive : GEvent::WindowBecameInactive));
}
+void GEventLoop::handle_window_close_request_event(const GUI_Event&, GWindow& window)
+{
+ post_event(&window, make<GEvent>(GEvent::WindowCloseRequest));
+}
+
void GEventLoop::handle_key_event(const GUI_Event& event, GWindow& window)
{
#ifdef GEVENTLOOP_DEBUG
@@ -192,6 +206,9 @@ void GEventLoop::wait_for_event()
case GUI_Event::Type::WindowDeactivated:
handle_window_activation_event(event, *window);
break;
+ case GUI_Event::Type::WindowCloseRequest:
+ handle_window_close_request_event(event, *window);
+ break;
case GUI_Event::Type::KeyDown:
case GUI_Event::Type::KeyUp:
handle_key_event(event, *window);
diff --git a/LibGUI/GEventLoop.h b/LibGUI/GEventLoop.h
index e4063bf64b..ad5fce26c1 100644
--- a/LibGUI/GEventLoop.h
+++ b/LibGUI/GEventLoop.h
@@ -27,12 +27,15 @@ public:
int register_timer(GObject&, int milliseconds, bool should_reload);
bool unregister_timer(int timer_id);
+ void exit(int);
+
private:
void wait_for_event();
void handle_paint_event(const GUI_Event&, GWindow&);
void handle_mouse_event(const GUI_Event&, GWindow&);
void handle_key_event(const GUI_Event&, GWindow&);
void handle_window_activation_event(const GUI_Event&, GWindow&);
+ void handle_window_close_request_event(const GUI_Event&, GWindow&);
void get_next_timer_expiration(timeval&);
@@ -44,6 +47,8 @@ private:
int m_event_fd { -1 };
bool m_running { false };
+ bool m_exit_requested { false };
+ int m_exit_code { 0 };
int m_next_timer_id { 1 };
diff --git a/LibGUI/GObject.cpp b/LibGUI/GObject.cpp
index be8627dc86..985d459c00 100644
--- a/LibGUI/GObject.cpp
+++ b/LibGUI/GObject.cpp
@@ -12,6 +12,7 @@ GObject::GObject(GObject* parent)
GObject::~GObject()
{
+ stop_timer();
if (m_parent)
m_parent->remove_child(*this);
auto children_to_delete = move(m_children);
diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp
index 9b92536679..f5ec3cfc77 100644
--- a/LibGUI/GWindow.cpp
+++ b/LibGUI/GWindow.cpp
@@ -35,11 +35,17 @@ GWindow::GWindow(GObject* parent)
GWindow::~GWindow()
{
+ if (m_main_widget)
+ delete m_main_widget;
hide();
}
void GWindow::close()
{
+ // FIXME: If we exit the event loop, we're never gonna deal with the delete_later request!
+ // This will become relevant once we support nested event loops.
+ if (should_exit_app_on_close())
+ GEventLoop::main().exit(0);
delete_later();
}
@@ -160,6 +166,11 @@ void GWindow::event(GEvent& event)
return;
}
+ if (event.type() == GEvent::WindowCloseRequest) {
+ close();
+ return;
+ }
+
GObject::event(event);
}
diff --git a/LibGUI/GWindow.h b/LibGUI/GWindow.h
index dc2549ee9f..33af06f2bb 100644
--- a/LibGUI/GWindow.h
+++ b/LibGUI/GWindow.h
@@ -53,6 +53,9 @@ public:
GWidget* global_cursor_tracking_widget() { return m_global_cursor_tracking_widget.ptr(); }
const GWidget* global_cursor_tracking_widget() const { return m_global_cursor_tracking_widget.ptr(); }
+ bool should_exit_app_on_close() const { return m_should_exit_app_on_close; }
+ void set_should_exit_app_on_close(bool b) { m_should_exit_app_on_close = b; }
+
private:
RetainPtr<GraphicsBitmap> m_backing;
int m_window_id { 0 };
@@ -62,5 +65,6 @@ private:
WeakPtr<GWidget> m_global_cursor_tracking_widget;
Rect m_rect_when_windowless;
String m_title_when_windowless;
+ bool m_should_exit_app_on_close { false };
};
diff --git a/Terminal/main.cpp b/Terminal/main.cpp
index f6f2a47830..44b7b17d2a 100644
--- a/Terminal/main.cpp
+++ b/Terminal/main.cpp
@@ -149,6 +149,8 @@ int main(int, char**)
terminal.set_in_active_window(true);
} else if (event.type == GUI_Event::Type::WindowDeactivated) {
terminal.set_in_active_window(false);
+ } else if (event.type == GUI_Event::Type::WindowCloseRequest) {
+ return 0;
}
}
}
diff --git a/Userland/guitest2.cpp b/Userland/guitest2.cpp
index 92b254563b..54187c82a6 100644
--- a/Userland/guitest2.cpp
+++ b/Userland/guitest2.cpp
@@ -40,6 +40,7 @@ int main(int argc, char** argv)
#endif
auto* launcher_window = make_launcher_window();
+ launcher_window->set_should_exit_app_on_close(true);
launcher_window->show();
return loop.exec();
diff --git a/WindowServer/WSMessage.h b/WindowServer/WSMessage.h
index 87211001b0..f0c47897e2 100644
--- a/WindowServer/WSMessage.h
+++ b/WindowServer/WSMessage.h
@@ -22,6 +22,7 @@ public:
KeyUp,
WindowActivated,
WindowDeactivated,
+ WindowCloseRequest,
};
WSMessage() { }
diff --git a/WindowServer/WSWindow.cpp b/WindowServer/WSWindow.cpp
index 8b6414c8ec..e98a7ed368 100644
--- a/WindowServer/WSWindow.cpp
+++ b/WindowServer/WSWindow.cpp
@@ -118,6 +118,9 @@ void WSWindow::on_message(WSMessage& message)
case WSMessage::WindowDeactivated:
gui_event.type = GUI_Event::Type::WindowDeactivated;
break;
+ case WSMessage::WindowCloseRequest:
+ gui_event.type = GUI_Event::Type::WindowCloseRequest;
+ break;
default:
break;
}
diff --git a/WindowServer/WSWindowManager.cpp b/WindowServer/WSWindowManager.cpp
index 225f0afce2..0912579891 100644
--- a/WindowServer/WSWindowManager.cpp
+++ b/WindowServer/WSWindowManager.cpp
@@ -36,6 +36,19 @@ static inline Rect title_bar_text_rect(const Rect& window)
};
}
+static inline Rect close_button_rect_for_window(const Rect& window_rect)
+{
+ auto titlebar_inner_rect = title_bar_text_rect(window_rect);
+ int close_button_margin = 1;
+ int close_button_size = titlebar_inner_rect.height() - close_button_margin * 2;
+ return Rect {
+ titlebar_inner_rect.right() - close_button_size,
+ titlebar_inner_rect.top() + close_button_margin,
+ close_button_size,
+ close_button_size
+ };
+}
+
static inline Rect border_window_rect(const Rect& window)
{
auto titlebar_rect = title_bar_rect(window);
@@ -152,6 +165,22 @@ WSWindowManager::~WSWindowManager()
{
}
+static const char* s_close_button_bitmap_data = {
+ " ## ## "
+ " ## ## "
+ " ## ## "
+ " ### "
+ " # "
+ " ### "
+ " ## ## "
+ " ## ## "
+ " ## ## "
+};
+
+static CharacterBitmap* s_close_button_bitmap;
+static const int s_close_button_bitmap_width = 11;
+static const int s_close_button_bitmap_height = 11;
+
void WSWindowManager::paint_window_frame(WSWindow& window)
{
LOCKER(m_lock);
@@ -204,6 +233,17 @@ void WSWindowManager::paint_window_frame(WSWindow& window)
m_back_painter->draw_rect(inner_border_rect, border_color);
m_back_painter->draw_text(titlebar_title_rect, window.title(), Painter::TextAlignment::CenterLeft, title_color);
+ Rect close_button_rect = close_button_rect_for_window(window.rect());
+ if (!s_close_button_bitmap)
+ s_close_button_bitmap = CharacterBitmap::create_from_ascii(s_close_button_bitmap_data, s_close_button_bitmap_width, s_close_button_bitmap_height).leak_ref();
+
+ m_back_painter->fill_rect_with_gradient(close_button_rect, Color::LightGray, Color::White);
+ m_back_painter->draw_rect(close_button_rect, Color::Black);
+ auto x_location = close_button_rect.location();
+ x_location.move_by(2, 2);
+ m_back_painter->draw_bitmap(x_location, *s_close_button_bitmap, Color::Black);
+
+
#ifdef DEBUG_WID_IN_TITLE_BAR
Color metadata_color(96, 96, 96);
m_back_painter->draw_text(
@@ -276,6 +316,15 @@ void WSWindowManager::handle_titlebar_mouse_event(WSWindow& window, WSMouseEvent
}
}
+void WSWindowManager::handle_close_button_mouse_event(WSWindow& window, WSMouseEvent& event)
+{
+ if (event.type() == WSMessage::MouseDown && event.button() == MouseButton::Left) {
+ WSMessage message(WSMessage::WindowCloseRequest);
+ window.on_message(message);
+ return;
+ }
+}
+
void WSWindowManager::process_mouse_event(WSMouseEvent& event)
{
if (event.type() == WSMessage::MouseUp && event.button() == MouseButton::Left) {
@@ -320,6 +369,10 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event)
move_to_front(*window);
set_active_window(window);
}
+ if (close_button_rect_for_window(window->rect()).contains(event.position())) {
+ handle_close_button_mouse_event(*window, event);
+ return;
+ }
handle_titlebar_mouse_event(*window, event);
return;
}
diff --git a/WindowServer/WSWindowManager.h b/WindowServer/WSWindowManager.h
index d55ea34791..dd3a7b17f3 100644
--- a/WindowServer/WSWindowManager.h
+++ b/WindowServer/WSWindowManager.h
@@ -48,6 +48,7 @@ private:
void process_mouse_event(WSMouseEvent&);
void handle_titlebar_mouse_event(WSWindow&, WSMouseEvent&);
+ void handle_close_button_mouse_event(WSWindow&, WSMouseEvent&);
void set_active_window(WSWindow*);