WindowServer+LibGUI: Add ability to set per-window icons.

The icons are passed around as filesystem paths for now, since the shared
memory bitmaps only support 2 sides.
This commit is contained in:
Andreas Kling 2019-04-13 16:59:55 +02:00
parent 7a74b76769
commit c09c114d77
19 changed files with 151 additions and 16 deletions

View file

@ -74,13 +74,14 @@ void TaskbarWindow::wm_event(GWMEvent& event)
}
case GEvent::WM_WindowStateChanged: {
auto& changed_event = static_cast<GWMWindowStateChangedEvent&>(event);
printf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u\n",
printf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u, icon_path=%s\n",
changed_event.client_id(),
changed_event.window_id(),
changed_event.title().characters(),
changed_event.rect().to_string().characters(),
changed_event.is_active(),
changed_event.is_minimized()
changed_event.is_minimized(),
changed_event.icon_path().characters()
);
if (!should_include_window(changed_event.window_type()))
break;
@ -89,6 +90,7 @@ void TaskbarWindow::wm_event(GWMEvent& event)
window.set_rect(changed_event.rect());
window.set_active(changed_event.is_active());
window.set_minimized(changed_event.is_minimized());
window.set_icon_path(changed_event.icon_path());
if (window.is_minimized()) {
window.button()->set_foreground_color(Color::DarkGray);
window.button()->set_caption(String::format("[%s]", changed_event.title().characters()));
@ -96,6 +98,7 @@ void TaskbarWindow::wm_event(GWMEvent& event)
window.button()->set_foreground_color(Color::Black);
window.button()->set_caption(changed_event.title());
}
window.button()->set_icon(window.icon());
window.button()->set_checked(changed_event.is_active());
break;
}

View file

@ -63,11 +63,27 @@ public:
void set_minimized(bool minimized) { m_minimized = minimized; }
bool is_minimized() const { return m_minimized; }
String icon_path() const { return m_icon_path; }
void set_icon_path(const String& icon_path)
{
if (m_icon_path == icon_path)
return;
auto icon = GraphicsBitmap::load_from_file(icon_path);
if (!icon)
return;
m_icon_path = icon_path;
m_icon = move(icon);
}
const GraphicsBitmap* icon() const { return m_icon.ptr(); }
private:
WindowIdentifier m_identifier;
String m_title;
Rect m_rect;
GButton* m_button { nullptr };
String m_icon_path;
RetainPtr<GraphicsBitmap> m_icon;
bool m_active { false };
bool m_minimized { false };
};

View file

@ -151,5 +151,7 @@ int main(int argc, char** argv)
text_editor->set_focus(true);
window->show();
window->set_icon_path("/res/icons/TextEditor16.png");
return app.exec();
}

View file

@ -52,5 +52,7 @@ int main(int argc, char** argv)
window->show();
window->set_icon_path("/res/icons/minesweeper/mine.png");
return app.exec();
}

View file

@ -45,10 +45,10 @@ void GButton::paint_event(GPaintEvent& event)
auto content_rect = rect().shrunken(10, 2);
auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Point();
if (m_being_pressed) {
content_rect.move_by(1, 1);
icon_location.move_by(1, 1);
}
if (m_icon && !m_caption.is_empty())
icon_location.set_x(content_rect.x());
if (m_being_pressed)
painter.translate(1, 1);
if (m_icon) {
if (is_enabled())
painter.blit(icon_location, *m_icon, m_icon->rect());
@ -56,6 +56,10 @@ void GButton::paint_event(GPaintEvent& event)
painter.blit_dimmed(icon_location, *m_icon, m_icon->rect());
}
auto& font = (m_checkable && m_checked) ? Font::default_bold_font() : this->font();
if (m_icon && !m_caption.is_empty()) {
content_rect.move_by(m_icon->width() + 4, 0);
content_rect.set_width(content_rect.width() - m_icon->width() - 4);
}
painter.draw_text(content_rect, m_caption, font, text_alignment(), foreground_color(), TextElision::Right);
}
@ -130,3 +134,11 @@ void GButton::set_action(GAction& action)
action.register_button({ }, *this);
set_enabled(action.is_enabled());
}
void GButton::set_icon(RetainPtr<GraphicsBitmap>&& icon)
{
if (m_icon.ptr() == icon.ptr())
return;
m_icon = move(icon);
update();
}

View file

@ -17,7 +17,7 @@ public:
String caption() const { return m_caption; }
void set_caption(const String&);
void set_icon(RetainPtr<GraphicsBitmap>&& icon) { m_icon = move(icon); }
void set_icon(RetainPtr<GraphicsBitmap>&&);
const GraphicsBitmap* icon() const { return m_icon.ptr(); }
GraphicsBitmap* icon() { return m_icon.ptr(); }

View file

@ -69,12 +69,13 @@ public:
class GWMWindowStateChangedEvent : public GWMEvent {
public:
GWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, GWindowType window_type, bool is_minimized)
GWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, GWindowType window_type, bool is_minimized, const String& icon_path)
: GWMEvent(GEvent::Type::WM_WindowStateChanged, client_id, window_id)
, m_title(title)
, m_icon_path(icon_path)
, m_rect(rect)
, m_active(is_active)
, m_window_type(window_type)
, m_active(is_active)
, m_minimized(is_minimized)
{
}
@ -84,12 +85,14 @@ public:
bool is_active() const { return m_active; }
GWindowType window_type() const { return m_window_type; }
bool is_minimized() const { return m_minimized; }
String icon_path() const { return m_icon_path; }
private:
String m_title;
String m_icon_path;
Rect m_rect;
bool m_active;
GWindowType m_window_type;
bool m_active;
bool m_minimized;
};

View file

@ -174,7 +174,7 @@ void GEventLoop::handle_wm_event(const WSAPI_ServerMessage& event, GWindow& wind
dbgprintf("GEventLoop: handle_wm_event: %d\n", (int)event.type);
#endif
if (event.type == WSAPI_ServerMessage::WM_WindowStateChanged)
return post_event(window, make<GWMWindowStateChangedEvent>(event.wm.client_id, event.wm.window_id, String(event.text, event.text_length), event.wm.rect, event.wm.is_active, (GWindowType)event.wm.window_type, event.wm.is_minimized));
return post_event(window, make<GWMWindowStateChangedEvent>(event.wm.client_id, event.wm.window_id, String(event.text, event.text_length), event.wm.rect, event.wm.is_active, (GWindowType)event.wm.window_type, event.wm.is_minimized, String(event.wm.icon_path, event.wm.icon_path_length)));
if (event.type == WSAPI_ServerMessage::WM_WindowRemoved)
return post_event(window, make<GWMWindowRemovedEvent>(event.wm.client_id, event.wm.window_id));
ASSERT_NOT_REACHED();

View file

@ -448,3 +448,19 @@ void GWindow::set_modal(bool 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);
}

View file

@ -100,6 +100,9 @@ public:
void set_override_cursor(GStandardCursor);
String icon_path() const { return m_icon_path; }
void set_icon_path(const String&);
virtual const char* class_name() const override { return "GWindow"; }
protected:
@ -123,6 +126,7 @@ private:
WeakPtr<GWidget> m_hovered_widget;
Rect m_rect_when_windowless;
String m_title_when_windowless;
String m_icon_path;
Vector<Rect> m_pending_paint_event_rects;
Size m_size_increment;
Size m_base_size;

View file

@ -116,6 +116,8 @@ struct WSAPI_ServerMessage {
bool is_active;
bool is_minimized;
WSAPI_WindowType window_type;
int icon_path_length;
char icon_path[256];
} wm;
struct {
WSAPI_Rect rect;
@ -193,6 +195,7 @@ struct WSAPI_ClientMessage {
SetWindowOverrideCursor,
WM_SetActiveWindow,
PopupMenu,
SetWindowIcon,
};
Type type { Invalid };
int window_id { -1 };

View file

@ -339,6 +339,28 @@ void WSClientConnection::handle_request(const WSAPIGetWindowTitleRequest& reques
post_message(response);
}
void WSClientConnection::handle_request(const WSAPISetWindowIconRequest& request)
{
int window_id = request.window_id();
auto it = m_windows.find(window_id);
if (it == m_windows.end()) {
post_error("WSAPISetWindowIconRequest: Bad window ID");
return;
}
auto& window = *(*it).value;
if (request.icon_path().is_empty()) {
window.set_default_icon();
} else {
auto icon = GraphicsBitmap::load_from_file(request.icon_path());
if (!icon)
return;
window.set_icon(request.icon_path(), *icon);
}
window.frame().invalidate_title_bar();
WSWindowManager::the().tell_wm_listeners_window_state_changed(window);
}
void WSClientConnection::handle_request(const WSAPISetWindowRectRequest& request)
{
int window_id = request.window_id();
@ -601,6 +623,8 @@ void WSClientConnection::on_request(const WSAPIClientRequest& request)
return handle_request(static_cast<const WSAPISetWindowRectRequest&>(request));
case WSMessage::APIGetWindowRectRequest:
return handle_request(static_cast<const WSAPIGetWindowRectRequest&>(request));
case WSMessage::APISetWindowIconRequest:
return handle_request(static_cast<const WSAPISetWindowIconRequest&>(request));
case WSMessage::APISetClipboardContentsRequest:
return handle_request(static_cast<const WSAPISetClipboardContentsRequest&>(request));
case WSMessage::APIGetClipboardContentsRequest:

View file

@ -56,6 +56,7 @@ private:
void handle_request(const WSAPIGetWindowTitleRequest&);
void handle_request(const WSAPISetWindowRectRequest&);
void handle_request(const WSAPIGetWindowRectRequest&);
void handle_request(const WSAPISetWindowIconRequest&);
void handle_request(const WSAPISetClipboardContentsRequest&);
void handle_request(const WSAPIGetClipboardContentsRequest&);
void handle_request(const WSAPICreateWindowRequest&);

View file

@ -45,6 +45,7 @@ public:
APIGetWindowTitleRequest,
APISetWindowRectRequest,
APIGetWindowRectRequest,
APISetWindowIconRequest,
APIInvalidateRectRequest,
APIDidFinishPaintingNotification,
APIGetWindowBackingStoreRequest,
@ -463,6 +464,23 @@ private:
Rect m_rect;
};
class WSAPISetWindowIconRequest final : public WSAPIClientRequest {
public:
explicit WSAPISetWindowIconRequest(int client_id, int window_id, const String& icon_path)
: WSAPIClientRequest(WSMessage::APISetWindowIconRequest, client_id)
, m_window_id(window_id)
, m_icon_path(icon_path)
{
}
int window_id() const { return m_window_id; }
String icon_path() const { return m_icon_path; }
private:
int m_window_id { 0 };
String m_icon_path;
};
class WSAPIGetWindowRectRequest final : public WSAPIClientRequest {
public:
explicit WSAPIGetWindowRectRequest(int client_id, int window_id)
@ -684,9 +702,10 @@ public:
class WSWMWindowStateChangedEvent : public WSWMEvent {
public:
WSWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, WSWindowType window_type, bool is_minimized)
WSWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, WSWindowType window_type, bool is_minimized, const String& icon_path)
: WSWMEvent(WSMessage::WM_WindowStateChanged, client_id, window_id)
, m_title(title)
, m_icon_path(icon_path)
, m_rect(rect)
, m_active(is_active)
, m_window_type(window_type)
@ -695,6 +714,7 @@ public:
}
String title() const { return m_title; }
String icon_path() const { return m_icon_path; }
Rect rect() const { return m_rect; }
bool is_active() const { return m_active; }
WSWindowType window_type() const { return m_window_type; }
@ -702,6 +722,7 @@ public:
private:
String m_title;
String m_icon_path;
Rect m_rect;
bool m_active;
WSWindowType m_window_type;

View file

@ -293,6 +293,10 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess
case WSAPI_ClientMessage::Type::PopupMenu:
post_message(client, make<WSAPIPopupMenuRequest>(client_id, message.menu.menu_id, message.menu.position));
break;
case WSAPI_ClientMessage::Type::SetWindowIcon:
ASSERT(message.text_length < (ssize_t)sizeof(message.text));
post_message(client, make<WSAPISetWindowIconRequest>(client_id, message.window_id, String(message.text, message.text_length)));
break;
case WSAPI_ClientMessage::Type::DestroyMenu:
post_message(client, make<WSAPIDestroyMenuRequest>(client_id, message.menu.menu_id));
break;

View file

@ -5,11 +5,16 @@
#include <WindowServer/WSAPITypes.h>
#include <WindowServer/WSClientConnection.h>
static String default_window_icon_path()
{
return "/res/icons/16x16/window.png";
}
static GraphicsBitmap& default_window_icon()
{
static GraphicsBitmap* s_icon;
if (!s_icon)
s_icon = GraphicsBitmap::load_from_file("/res/icons/16x16/window.png").leak_ref();
s_icon = GraphicsBitmap::load_from_file(default_window_icon_path()).leak_ref();
return *s_icon;
}
@ -17,6 +22,7 @@ WSWindow::WSWindow(WSMessageReceiver& internal_owner, WSWindowType type)
: m_internal_owner(&internal_owner)
, m_type(type)
, m_icon(default_window_icon())
, m_icon_path(default_window_icon_path())
, m_frame(*this)
{
WSWindowManager::the().add_window(*this);
@ -28,6 +34,7 @@ WSWindow::WSWindow(WSClientConnection& client, WSWindowType window_type, int win
, m_modal(modal)
, m_window_id(window_id)
, m_icon(default_window_icon())
, m_icon_path(default_window_icon_path())
, m_frame(*this)
{
// FIXME: This should not be hard-coded here.
@ -189,6 +196,9 @@ void WSWindow::on_message(const WSMessage& message)
memcpy(server_message.text, changed_event.title().characters(), changed_event.title().length());
server_message.text_length = changed_event.title().length();
server_message.wm.rect = changed_event.rect();
ASSERT(changed_event.icon_path().length() < sizeof(server_message.wm.icon_path));
memcpy(server_message.wm.icon_path, changed_event.icon_path().characters(), changed_event.icon_path().length());
server_message.wm.icon_path_length = changed_event.icon_path().length();
break;
}
@ -236,3 +246,9 @@ bool WSWindow::is_blocked_by_modal_window() const
{
return !is_modal() && client() && client()->is_showing_modal_window();
}
void WSWindow::set_default_icon()
{
m_icon = default_window_icon();
m_icon_path = default_window_icon_path();
}

View file

@ -113,7 +113,13 @@ public:
void set_base_size(const Size& size) { m_base_size = size; }
const GraphicsBitmap& icon() const { return *m_icon; }
void set_icon(Retained<GraphicsBitmap>&& icon) { m_icon = move(icon); }
String icon_path() const { return m_icon_path; }
void set_icon(const String& path, Retained<GraphicsBitmap>&& icon)
{
m_icon_path = path;
m_icon = move(icon);
}
void set_default_icon();
const WSCursor* override_cursor() const { return m_override_cursor.ptr(); }
void set_override_cursor(RetainPtr<WSCursor>&& cursor) { m_override_cursor = move(cursor); }
@ -146,6 +152,7 @@ private:
Size m_size_increment;
Size m_base_size;
Retained<GraphicsBitmap> m_icon;
String m_icon_path;
RetainPtr<WSCursor> m_override_cursor;
WSWindowFrame m_frame;
Color m_background_color { Color::LightGray };

View file

@ -363,7 +363,7 @@ void WSWindowManager::remove_window(WSWindow& window)
void WSWindowManager::tell_wm_listener_about_window(WSWindow& listener, WSWindow& window)
{
if (window.client())
WSMessageLoop::the().post_message(listener, make<WSWMWindowStateChangedEvent>(window.client()->client_id(), window.window_id(), window.title(), window.rect(), window.is_active(), window.type(), window.is_minimized()));
WSMessageLoop::the().post_message(listener, make<WSWMWindowStateChangedEvent>(window.client()->client_id(), window.window_id(), window.title(), window.rect(), window.is_active(), window.type(), window.is_minimized(), window.icon_path()));
}
void WSWindowManager::tell_wm_listeners_window_state_changed(WSWindow& window)

View file

@ -111,6 +111,8 @@ public:
bool any_opaque_window_contains_rect(const Rect&);
bool any_opaque_window_above_this_one_contains_rect(const WSWindow&, const Rect&);
void tell_wm_listeners_window_state_changed(WSWindow&);
private:
void process_mouse_event(const WSMouseEvent&, WSWindow*& event_window);
bool process_ongoing_window_resize(const WSMouseEvent&, WSWindow*& event_window);
@ -135,7 +137,6 @@ private:
void paint_window_frame(const WSWindow&);
void flip_buffers();
void tick_clock();
void tell_wm_listeners_window_state_changed(WSWindow&);
void tell_wm_listener_about_window(WSWindow& listener, WSWindow&);
void pick_new_active_window();