From 4f6faddbab522fd868eec5216db75e416a2ba5d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 6 Apr 2024 08:23:07 +0530 Subject: [PATCH] Implement rendering of window control buttons in CSD They still need to be wired up --- glfw/glfw3.h | 2 +- glfw/wl_client_side_decorations.c | 29 +++++++++++++++++++----- glfw/wl_init.c | 6 +++-- glfw/wl_platform.h | 10 +++++++++ glfw/wl_window.c | 37 +++++++++++++++++++++++++++++-- kitty/freetype_render_ui_text.c | 14 ++++++++---- kitty/freetype_render_ui_text.h | 2 +- kitty/glfw-wrapper.h | 2 +- kitty/glfw.c | 11 +++++---- 9 files changed, 93 insertions(+), 20 deletions(-) diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 925098267..c12979a6c 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1785,7 +1785,7 @@ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); -typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin); +typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef char* (* GLFWcurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void); typedef void (* GLFWclipboarddatafreefun)(void* data); diff --git a/glfw/wl_client_side_decorations.c b/glfw/wl_client_side_decorations.c index 53a8160e5..177f1920b 100644 --- a/glfw/wl_client_side_decorations.c +++ b/glfw/wl_client_side_decorations.c @@ -164,9 +164,10 @@ create_shadow_tile(_GLFWwindow *window) { static void render_title_bar(_GLFWwindow *window, bool to_front_buffer) { const bool is_focused = window->id == _glfw.focusedWindowId; - uint32_t light_fg = is_focused ? 0xff444444 : 0xff888888, light_bg = is_focused ? 0xffdddad6 : 0xffeeeeee; - uint32_t dark_fg = is_focused ? 0xffffffff : 0xffcccccc, dark_bg = is_focused ? 0xff303030 : 0xff242424; - uint32_t bg_color = light_bg, fg_color = light_fg; + const uint32_t light_fg = is_focused ? 0xff444444 : 0xff888888, light_bg = is_focused ? 0xffdddad6 : 0xffeeeeee; + const uint32_t dark_fg = is_focused ? 0xffffffff : 0xffcccccc, dark_bg = is_focused ? 0xff303030 : 0xff242424; + static const uint32_t hover_dark_bg = 0xff444444, hover_light_bg = 0xffcccccc; + uint32_t bg_color = light_bg, fg_color = light_fg, hover_bg = hover_light_bg; GLFWColorScheme appearance = glfwGetCurrentSystemColorTheme(); if (decs.use_custom_titlebar_color || appearance == GLFW_COLOR_SCHEME_NO_PREFERENCE) { bg_color = 0xff000000 | (decs.titlebar_color & 0xffffff); @@ -174,7 +175,7 @@ render_title_bar(_GLFWwindow *window, bool to_front_buffer) { double green = ((bg_color >> 8) & 0xFF) / 255.0; double blue = (bg_color & 0xFF) / 255.0; double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue; - if (luma < 0.5) fg_color = dark_fg; + if (luma < 0.5) { fg_color = dark_fg; hover_bg = hover_dark_bg; } if (!decs.use_custom_titlebar_color) bg_color = luma < 0.5 ? dark_bg : light_bg; } else if (appearance == GLFW_COLOR_SCHEME_DARK) { bg_color = dark_bg; fg_color = dark_fg; } uint8_t *output = to_front_buffer ? decs.top.buffer.data.front : decs.top.buffer.data.back; @@ -198,12 +199,30 @@ render_title_bar(_GLFWwindow *window, bool to_front_buffer) { } // render text part + int button_size = (int)roundf(decs.metrics.visible_titlebar_height * decs.for_window_state.fscale); + int num_buttons = 1; + if (window->wl.wm_capabilities.maximize) num_buttons++; + if (window->wl.wm_capabilities.minimize) num_buttons++; output += decs.top.buffer.stride * margin; if (window->wl.title && window->wl.title[0] && _glfw.callbacks.draw_text) { - if (_glfw.callbacks.draw_text((GLFWwindow*)window, window->wl.title, fg_color, bg_color, output, decs.top.buffer.width, decs.top.buffer.height - margin, 0, 0, 0)) return; + if (_glfw.callbacks.draw_text((GLFWwindow*)window, window->wl.title, fg_color, bg_color, output, decs.top.buffer.width, decs.top.buffer.height - margin, 0, 0, num_buttons * button_size, false)) goto render_buttons; } // rendering of text failed, blank the buffer for (uint32_t *px = (uint32_t*)output, *end = (uint32_t*)(output + decs.top.buffer.size_in_bytes); px < end; px++) *px = bg_color; + +render_buttons: + decs.maximize.width = 0; decs.minimize.width = 0; decs.close.width = 0; + if (!button_size) return; + int left = decs.top.buffer.width - num_buttons * button_size; +#define draw(which, text, hover_bg) { \ + _glfw.callbacks.draw_text((GLFWwindow*)window, text, fg_color, decs.which.hovered ? hover_bg : bg_color, output, decs.top.buffer.width, \ + decs.top.buffer.height - margin, /*x=*/left, /*y=*/0, /*right_margin=*/decs.top.buffer.width - left - button_size, true); \ + decs.which.left = left; decs.which.width = button_size; left += button_size; \ +} + if (window->wl.wm_capabilities.minimize) draw(minimize, "🗕", hover_bg); + if (window->wl.wm_capabilities.maximize) draw(maximize, "🗖", hover_bg); + draw(close, "🗙", 0xffff0000); +#undef draw } static void diff --git a/glfw/wl_init.c b/glfw/wl_init.c index bf50c1b8d..487ea6dd8 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -675,8 +675,10 @@ static void registryHandleGlobal(void* data UNUSED, } else if (is(xdg_wm_base)) { - _glfw.wl.wmBase = - wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + version = min(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION, version); +#endif + _glfw.wl.wmBase = wl_registry_bind(registry, name, &xdg_wm_base_interface, version); xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL); } else if (is(zxdg_decoration_manager_v1)) diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 1ad8a406f..8c78828ab 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -166,6 +166,7 @@ typedef struct _GLFWwindowWayland struct xdg_surface* surface; struct xdg_toplevel* toplevel; struct zxdg_toplevel_decoration_v1* decoration; + struct { int width, height; } top_level_bounds; } xdg; struct wp_fractional_scale_v1 *wp_fractional_scale_v1; struct wp_viewport *wp_viewport; @@ -224,6 +225,10 @@ typedef struct _GLFWwindowWayland int32_t x, y, width, height; } geometry; + struct { + bool hovered; + int width, left; + } minimize, maximize, close; struct { uint32_t *data; size_t for_decoration_size, stride, segments, corner_size; @@ -244,6 +249,11 @@ typedef struct _GLFWwindowWayland int32_t width, height; } user_requested_content_size; + struct { + bool minimize, maximize, fullscreen, window_menu; + } wm_capabilities; + + bool maximize_on_first_show; // counters for ignoring axis events following axis_discrete events in the // same frame along the same axis diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 3269869c8..420c692ab 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -696,9 +696,42 @@ static void xdgToplevelHandleClose(void* data, _glfwInputWindowCloseRequest(window); } +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) +static void +xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, struct wl_array *caps) { + _GLFWwindow *window = data; +#define c (window->wl.wm_capabilities) + memset(&c, 0, sizeof(c)); + + enum xdg_toplevel_wm_capabilities *cap; + wl_array_for_each(cap, caps) { + switch (*cap) { + case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: c.maximize = true; break; + case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: c.minimize = true; break; + case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: c.window_menu = true; break; + case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: c.fullscreen = true; break; + } + } + debug("Compositor top-level capabilities: maximize=%d minimize=%d window_menu=%d fullscreen=%d\n", + c.maximize, c.minimize, c.window_menu, c.fullscreen); +#undef c +} +#endif + +static void +xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, int32_t width, int32_t height) { + _GLFWwindow *window = data; + window->wl.xdg.top_level_bounds.width = width; + window->wl.xdg.top_level_bounds.height = height; +} + static const struct xdg_toplevel_listener xdgToplevelListener = { - xdgToplevelHandleConfigure, - xdgToplevelHandleClose + .configure = xdgToplevelHandleConfigure, + .close = xdgToplevelHandleClose, +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + .configure_bounds = xdg_toplevel_configure_bounds, + .wm_capabilities = xdg_toplevel_wm_capabilities, +#endif }; static void diff --git a/kitty/freetype_render_ui_text.c b/kitty/freetype_render_ui_text.c index c0d35d549..5f44f63d7 100644 --- a/kitty/freetype_render_ui_text.c +++ b/kitty/freetype_render_ui_text.c @@ -148,11 +148,12 @@ typedef struct RenderState { pixel *output; size_t output_width, output_height, stride; Face *current_face; - float x, y; + float x, y, start_pos_for_current_run; int y_offset; Region src, dest; unsigned sz_px; bool truncated; + bool horizontally_center; } RenderState; static void @@ -162,6 +163,10 @@ setup_regions(ProcessedBitmap *bm, RenderState *rs, int baseline) { int xoff = (int)(rs->x + bm->bitmap_left); if (xoff < 0) rs->src.left += -xoff; else rs->dest.left = xoff; + if (rs->horizontally_center) { + int run_width = (int)(rs->output_width - rs->start_pos_for_current_run); + rs->dest.left = (int)rs->start_pos_for_current_run + (run_width > (int)bm->width ? (run_width - bm->width)/2 : 0); + } int yoff = (int)(rs->y + bm->bitmap_top); if ((yoff > 0 && yoff > baseline)) { rs->dest.top = 0; @@ -325,6 +330,7 @@ render_run(RenderCtx *ctx, RenderState *rs) { rs->truncated = true; } + rs->start_pos_for_current_run = rs->x; for (unsigned int i = 0; i < limit; i++) { rs->x += (float)positions[i].x_offset / 64.0f; rs->y += (float)positions[i].y_offset / 64.0f; @@ -427,7 +433,7 @@ process_codepoint(RenderCtx *ctx, RenderState *rs, char_type codep, char_type ne } bool -render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin) { +render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool horizontally_center_runs) { RenderCtx *ctx = (RenderCtx*)ctx_; if (!ctx->created) return false; size_t output_width = right_margin <= width ? width - right_margin : 0; @@ -449,7 +455,7 @@ render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pix set_pixel_size(ctx, &main_face, sz_px, true); unsigned text_height = font_units_to_pixels_y(main_face.freetype, main_face.freetype->height); RenderState rs = { - .current_face = &main_face, .fg = fg, .bg = bg, + .current_face = &main_face, .fg = fg, .bg = bg, .horizontally_center = horizontally_center_runs, .output_width = output_width, .output_height = height, .stride = width, .output = (pixel*)output_buf, .x = x_offset, .y = y_offset, .sz_px = sz_px }; @@ -584,7 +590,7 @@ render_line(PyObject *self UNUSED, PyObject *args, PyObject *kw) { uint8_t *buffer = (uint8_t*) PyBytes_AS_STRING(ans); RenderCtx *ctx = (RenderCtx*)create_freetype_render_context(family, bold, italic); if (!ctx) return NULL; - if (!render_single_line((FreeTypeRenderCtx)ctx, text, 3 * height / 4, 0, 0xffffffff, buffer, width, height, x_offset, y_offset, right_margin)) { + if (!render_single_line((FreeTypeRenderCtx)ctx, text, 3 * height / 4, 0, 0xffffffff, buffer, width, height, x_offset, y_offset, right_margin, false)) { Py_CLEAR(ans); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, "Unknown error while rendering text"); ans = NULL; diff --git a/kitty/freetype_render_ui_text.h b/kitty/freetype_render_ui_text.h index f62efd839..5c7d4366b 100644 --- a/kitty/freetype_render_ui_text.h +++ b/kitty/freetype_render_ui_text.h @@ -13,7 +13,7 @@ typedef struct {bool created;} *FreeTypeRenderCtx; FreeTypeRenderCtx create_freetype_render_context(const char *family, bool bold, bool italic); void set_main_face_family(FreeTypeRenderCtx ctx, const char *family, bool bold, bool italic); -bool render_single_line(FreeTypeRenderCtx ctx, const char *text, unsigned sz_px, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin); +bool render_single_line(FreeTypeRenderCtx ctx, const char *text, unsigned sz_px, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool horizontally_center_runs); uint8_t* render_single_ascii_char_as_mask(FreeTypeRenderCtx ctx_, const char ch, size_t *result_width, size_t *result_height); void release_freetype_render_context(FreeTypeRenderCtx ctx); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index ab6303488..62b3f7bf5 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1523,7 +1523,7 @@ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); -typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin); +typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef char* (* GLFWcurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void); typedef void (* GLFWclipboarddatafreefun)(void* data); diff --git a/kitty/glfw.c b/kitty/glfw.c index 3c425fc40..e0743ab1b 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -663,7 +663,7 @@ ensure_csd_title_render_ctx(void) { } static bool -draw_text_callback(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin) { +draw_text_callback(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph) { if (!set_callback_window(window)) return false; if (!ensure_csd_title_render_ctx()) return false; double xdpi, ydpi; @@ -671,8 +671,11 @@ draw_text_callback(GLFWwindow *window, const char *text, uint32_t fg, uint32_t b unsigned px_sz = (unsigned)(global_state.callback_os_window->fonts_data->font_sz_in_pts * ydpi / 72.); px_sz = MIN(px_sz, 3 * height / 4); static char title[2048]; - snprintf(title, sizeof(title), " ❭ %s", text); - bool ok = render_single_line(csd_title_render_ctx, title, px_sz, fg, bg, output_buf, width, height, x_offset, y_offset, right_margin); + if (!is_single_glyph) { + snprintf(title, sizeof(title), " ❭ %s", text); + text = title; + } + bool ok = render_single_line(csd_title_render_ctx, text, px_sz, fg, bg, output_buf, width, height, x_offset, y_offset, right_margin, is_single_glyph); if (!ok && PyErr_Occurred()) PyErr_Print(); return ok; } @@ -685,7 +688,7 @@ draw_window_title(OSWindow *window, const char *text, color_type fg, color_type unsigned px_sz = (unsigned)(window->fonts_data->font_sz_in_pts * window->fonts_data->logical_dpi_y / 72.); px_sz = MIN(px_sz, 3 * height / 4); #define RGB2BGR(x) (x & 0xFF000000) | ((x & 0xFF0000) >> 16) | (x & 0x00FF00) | ((x & 0x0000FF) << 16) - bool ok = render_single_line(csd_title_render_ctx, buf, px_sz, RGB2BGR(fg), RGB2BGR(bg), output_buf, width, height, 0, 0, 0); + bool ok = render_single_line(csd_title_render_ctx, buf, px_sz, RGB2BGR(fg), RGB2BGR(bg), output_buf, width, height, 0, 0, 0, false); #undef RGB2BGR if (!ok && PyErr_Occurred()) PyErr_Print(); return ok;