mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-06-19 14:33:16 -04:00
feat: add dispatch togglejump
This commit is contained in:
parent
5c6cf39901
commit
bb909cc2ed
10 changed files with 544 additions and 147 deletions
|
|
@ -161,6 +161,7 @@ bindr=Super,Super_L,spawn,rofi -show run
|
|||
| `reload_config` | - | Hot-reload configuration. |
|
||||
| `quit` | - | Exit mangowm. |
|
||||
| `toggleoverview` | - | Toggle overview mode. |
|
||||
| `toggleojump` | - | Toggle overview with jump mode. |
|
||||
| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). |
|
||||
| `destroy_all_virtual_output` | - | Destroy all virtual monitors. |
|
||||
| `toggleoverlay` | - | Toggle overlay state for the focused window. |
|
||||
|
|
|
|||
|
|
@ -52,6 +52,18 @@ You can also color-code windows based on their state:
|
|||
|
||||
> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration.
|
||||
|
||||
### Overview Jump Mode
|
||||
| Setting | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `jump_hit_fg_color` | `0xc4939dff` | label text color. |
|
||||
| `jump_hit_bg_color` | `0x201b14ff` | label background color.
|
||||
| `jump_hit_border_color` | `0x8BAA9Bff` | label border color.
|
||||
| `jump_hit_border_width` | `4` | label border width.
|
||||
| `jump_hit_corner_radius` | `5` | label corner radius.
|
||||
| `jump_hit_padding_x` | `10` | label horizontal padding.
|
||||
| `jump_hit_padding_y` | `10` | label vertical padding.
|
||||
| `jump_hit_font_desc` | `monospace Bold 24` | label font set.|
|
||||
|
||||
## Cursor Theme
|
||||
|
||||
Set the size and theme of your mouse cursor.
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ pcre2_dep = dependency('libpcre2-8')
|
|||
libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1')
|
||||
pixman_dep = dependency('pixman-1')
|
||||
cjson_dep = dependency('libcjson')
|
||||
cairo_dep = dependency('cairo')
|
||||
pango_dep = dependency('pango')
|
||||
pangocairo_dep = dependency('pangocairo')
|
||||
|
||||
# version info
|
||||
|
|
@ -113,8 +111,6 @@ executable('mango',
|
|||
pcre2_dep,
|
||||
pixman_dep,
|
||||
cjson_dep,
|
||||
cairo_dep,
|
||||
pango_dep,
|
||||
pangocairo_dep,
|
||||
],
|
||||
install : true,
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ typedef struct {
|
|||
int32_t enable_hotarea;
|
||||
int32_t ov_tab_mode;
|
||||
int32_t ov_no_resize;
|
||||
|
||||
int32_t overviewgappi;
|
||||
int32_t overviewgappo;
|
||||
uint32_t cursor_hide_timeout;
|
||||
|
|
@ -407,6 +408,7 @@ typedef struct {
|
|||
|
||||
struct xkb_context *ctx;
|
||||
struct xkb_keymap *keymap;
|
||||
JumphitData jumhitdata;
|
||||
} Config;
|
||||
|
||||
typedef int32_t (*FuncType)(const Arg *);
|
||||
|
|
@ -1030,6 +1032,9 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
|
|||
} else if (strcmp(func_name, "toggleoverview") == 0) {
|
||||
func = toggleoverview;
|
||||
(*arg).i = atoi(arg_value);
|
||||
} else if (strcmp(func_name, "togglejump") == 0) {
|
||||
func = togglejump;
|
||||
(*arg).i = atoi(arg_value);
|
||||
} else if (strcmp(func_name, "set_proportion") == 0) {
|
||||
func = set_proportion;
|
||||
(*arg).f = atof(arg_value);
|
||||
|
|
@ -1758,6 +1763,50 @@ bool parse_option(Config *config, char *key, char *value) {
|
|||
config->cursor_size = atoi(value);
|
||||
} else if (strcmp(key, "cursor_theme") == 0) {
|
||||
config->cursor_theme = strdup(value);
|
||||
} else if (strcmp(key, "jump_hit_fg_color") == 0) {
|
||||
int64_t color = parse_color(value);
|
||||
if (color == -1) {
|
||||
fprintf(stderr,
|
||||
"\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_fg_color "
|
||||
"format: %s\n",
|
||||
value);
|
||||
return false;
|
||||
} else {
|
||||
convert_hex_to_rgba(config->jumhitdata.fg_color, color);
|
||||
}
|
||||
} else if (strcmp(key, "jump_hit_font_desc") == 0) {
|
||||
config->jumhitdata.font_desc = strdup(value);
|
||||
} else if (strcmp(key, "jump_hit_bg_color") == 0) {
|
||||
int64_t color = parse_color(value);
|
||||
if (color == -1) {
|
||||
fprintf(stderr,
|
||||
"\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_bg_color "
|
||||
"format: %s\n",
|
||||
value);
|
||||
return false;
|
||||
} else {
|
||||
convert_hex_to_rgba(config->jumhitdata.bg_color, color);
|
||||
}
|
||||
} else if (strcmp(key, "jump_hit_border_color") == 0) {
|
||||
int64_t color = parse_color(value);
|
||||
if (color == -1) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_border_color "
|
||||
"format: %s\n",
|
||||
value);
|
||||
return false;
|
||||
} else {
|
||||
convert_hex_to_rgba(config->jumhitdata.border_color, color);
|
||||
}
|
||||
} else if (strcmp(key, "jump_hit_border_width") == 0) {
|
||||
config->jumhitdata.border_width = CLAMP_FLOAT(atoi(value), 0, 100);
|
||||
} else if (strcmp(key, "jump_hit_corner_radius") == 0) {
|
||||
config->jumhitdata.corner_radius = CLAMP_FLOAT(atoi(value), 0, 100);
|
||||
} else if (strcmp(key, "jump_hit_padding_x") == 0) {
|
||||
config->jumhitdata.padding_x = CLAMP_FLOAT(atoi(value), 0, 100);
|
||||
} else if (strcmp(key, "jump_hit_padding_y") == 0) {
|
||||
config->jumhitdata.padding_y = CLAMP_FLOAT(atoi(value), 0, 100);
|
||||
} else if (strcmp(key, "disable_while_typing") == 0) {
|
||||
config->disable_while_typing = atoi(value);
|
||||
} else if (strcmp(key, "left_handed") == 0) {
|
||||
|
|
@ -3247,6 +3296,11 @@ void free_config(void) {
|
|||
config.cursor_theme = NULL;
|
||||
}
|
||||
|
||||
if (config.jumhitdata.font_desc) {
|
||||
free((void *)config.jumhitdata.font_desc);
|
||||
config.jumhitdata.font_desc = NULL;
|
||||
}
|
||||
|
||||
if (config.tablet_map_to_mon) {
|
||||
free(config.tablet_map_to_mon);
|
||||
config.tablet_map_to_mon = NULL;
|
||||
|
|
@ -3437,6 +3491,15 @@ void override_config(void) {
|
|||
config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f);
|
||||
config.unfocused_opacity =
|
||||
CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f);
|
||||
|
||||
config.jumhitdata.border_width =
|
||||
CLAMP_INT(config.jumhitdata.border_width, 0, 100);
|
||||
config.jumhitdata.corner_radius =
|
||||
CLAMP_INT(config.jumhitdata.corner_radius, 0, 100);
|
||||
config.jumhitdata.padding_x =
|
||||
CLAMP_INT(config.jumhitdata.padding_x, 0, 100);
|
||||
config.jumhitdata.padding_y =
|
||||
CLAMP_INT(config.jumhitdata.padding_y, 0, 100);
|
||||
}
|
||||
|
||||
void set_value_default() {
|
||||
|
|
@ -3475,7 +3538,6 @@ void set_value_default() {
|
|||
config.log_level = WLR_ERROR;
|
||||
config.numlockon = 0;
|
||||
config.capslock = 0;
|
||||
|
||||
config.ov_tab_mode = 1;
|
||||
config.ov_no_resize = 1;
|
||||
config.hotarea_size = 10;
|
||||
|
|
@ -3613,6 +3675,23 @@ void set_value_default() {
|
|||
config.animation_curve_opafadeout[2] = 0.5;
|
||||
config.animation_curve_opafadeout[3] = 0.5;
|
||||
|
||||
config.jumhitdata.fg_color[0] = 0xc4 / 255.0f;
|
||||
config.jumhitdata.fg_color[1] = 0x93 / 255.0f;
|
||||
config.jumhitdata.fg_color[2] = 0x9d / 255.0f;
|
||||
config.jumhitdata.fg_color[3] = 1.0f;
|
||||
config.jumhitdata.bg_color[0] = 0x32 / 255.0f;
|
||||
config.jumhitdata.bg_color[1] = 0x32 / 255.0f;
|
||||
config.jumhitdata.bg_color[2] = 0x32 / 255.0f;
|
||||
config.jumhitdata.bg_color[3] = 1.0f;
|
||||
config.jumhitdata.border_color[0] = 0x8b / 255.0f;
|
||||
config.jumhitdata.border_color[1] = 0xaa / 255.0f;
|
||||
config.jumhitdata.border_color[2] = 0x9b / 255.0f;
|
||||
config.jumhitdata.border_color[3] = 1.0f;
|
||||
config.jumhitdata.border_width = 4;
|
||||
config.jumhitdata.corner_radius = 5;
|
||||
config.jumhitdata.padding_x = 10;
|
||||
config.jumhitdata.padding_y = 10;
|
||||
|
||||
config.rootcolor[0] = 0x32 / 255.0f;
|
||||
config.rootcolor[1] = 0x32 / 255.0f;
|
||||
config.rootcolor[2] = 0x32 / 255.0f;
|
||||
|
|
@ -3725,6 +3804,7 @@ bool parse_config(void) {
|
|||
config.tag_rules = NULL;
|
||||
config.tag_rules_count = 0;
|
||||
config.cursor_theme = NULL;
|
||||
config.jumhitdata.font_desc = NULL;
|
||||
config.tablet_map_to_mon = NULL;
|
||||
strcpy(config.keymode, "default");
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ int32_t restore_minimized(const Arg *arg);
|
|||
int32_t toggle_scratchpad(const Arg *arg);
|
||||
int32_t focusdir(const Arg *arg);
|
||||
int32_t toggleoverview(const Arg *arg);
|
||||
int32_t togglejump(const Arg *arg);
|
||||
int32_t set_proportion(const Arg *arg);
|
||||
int32_t switch_proportion_preset(const Arg *arg);
|
||||
int32_t zoom(const Arg *arg);
|
||||
|
|
|
|||
|
|
@ -1737,6 +1737,10 @@ int32_t toggleoverview(const Arg *arg) {
|
|||
uint32_t target;
|
||||
uint32_t visible_client_number = 0;
|
||||
|
||||
if (!selmon->isoverview && selmon->is_jump_mode) {
|
||||
finish_jump_mode(selmon);
|
||||
}
|
||||
|
||||
if (selmon->isoverview) {
|
||||
wl_list_for_each(c, &clients, link) if (c && c->mon == selmon &&
|
||||
!client_is_unmanaged(c) &&
|
||||
|
|
@ -1781,6 +1785,7 @@ int32_t toggleoverview(const Arg *arg) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
selmon->tagset[selmon->seltags] = target;
|
||||
wl_list_for_each(c, &clients, link) {
|
||||
if (c && c->mon == selmon && !c->iskilling &&
|
||||
|
|
@ -1794,6 +1799,24 @@ int32_t toggleoverview(const Arg *arg) {
|
|||
view(&(Arg){.ui = target}, false);
|
||||
fix_mon_tagset_from_overview(selmon);
|
||||
refresh_monitors_workspaces_status(selmon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t togglejump(const Arg *arg) {
|
||||
if (!selmon)
|
||||
return 0;
|
||||
|
||||
if (!selmon->isoverview) {
|
||||
begin_jump_mode(selmon);
|
||||
toggleoverview(arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (selmon->isoverview) {
|
||||
toggleoverview(arg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,140 +1,322 @@
|
|||
#include "text-node.h"
|
||||
#include <stdlib.h>
|
||||
#include <cairo.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <wlr/interfaces/wlr_buffer.h>
|
||||
#include <wayland-server-core.h>
|
||||
#include <drm_fourcc.h>
|
||||
#include <glib.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <stdlib.h>
|
||||
#include <wayland-server-core.h>
|
||||
#include <wlr/interfaces/wlr_buffer.h>
|
||||
|
||||
// 自定义 wlr_buffer,用于将 Cairo Surface 包装并注入 wlr_scene
|
||||
struct mango_text_buffer {
|
||||
struct wlr_buffer base;
|
||||
cairo_surface_t *surface;
|
||||
struct wlr_buffer base;
|
||||
cairo_surface_t *surface;
|
||||
};
|
||||
|
||||
// 全局字体描述缓存
|
||||
static GHashTable *font_desc_cache = NULL;
|
||||
|
||||
static PangoFontDescription *get_cached_font_desc(const char *font_desc) {
|
||||
if (!font_desc_cache) {
|
||||
font_desc_cache =
|
||||
g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
||||
(GDestroyNotify)pango_font_description_free);
|
||||
}
|
||||
|
||||
PangoFontDescription *desc =
|
||||
g_hash_table_lookup(font_desc_cache, font_desc);
|
||||
if (!desc) {
|
||||
desc = pango_font_description_from_string(font_desc);
|
||||
g_hash_table_insert(font_desc_cache, g_strdup(font_desc), desc);
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
void mango_text_global_finish(void) {
|
||||
if (font_desc_cache) {
|
||||
g_hash_table_destroy(font_desc_cache);
|
||||
font_desc_cache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// wlr_buffer 实现
|
||||
static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) {
|
||||
struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base);
|
||||
cairo_surface_destroy(buf->surface);
|
||||
free(buf);
|
||||
struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base);
|
||||
cairo_surface_destroy(buf->surface);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static bool text_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
|
||||
uint32_t flags,
|
||||
void **data,
|
||||
uint32_t *format,
|
||||
size_t *stride) {
|
||||
(void)flags; // 该参数当前无需使用
|
||||
struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base);
|
||||
*data = cairo_image_surface_get_data(buf->surface);
|
||||
*format = DRM_FORMAT_ARGB8888;
|
||||
*stride = cairo_image_surface_get_stride(buf->surface);
|
||||
return true;
|
||||
uint32_t flags, void **data,
|
||||
uint32_t *format,
|
||||
size_t *stride) {
|
||||
(void)flags;
|
||||
struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base);
|
||||
*data = cairo_image_surface_get_data(buf->surface);
|
||||
*format = DRM_FORMAT_ARGB8888;
|
||||
*stride = cairo_image_surface_get_stride(buf->surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {
|
||||
// Cairo 表面数据在 flush 后稳定,此处无需额外操作
|
||||
}
|
||||
static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {}
|
||||
|
||||
static const struct wlr_buffer_impl text_buffer_impl = {
|
||||
.destroy = text_buffer_destroy,
|
||||
.begin_data_ptr_access = text_buffer_begin_data_ptr_access,
|
||||
.end_data_ptr_access = text_buffer_end_data_ptr_access,
|
||||
.destroy = text_buffer_destroy,
|
||||
.begin_data_ptr_access = text_buffer_begin_data_ptr_access,
|
||||
.end_data_ptr_access = text_buffer_end_data_ptr_access,
|
||||
};
|
||||
|
||||
struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent) {
|
||||
struct mango_text_node *node = calloc(1, sizeof(*node));
|
||||
if (!node) return NULL;
|
||||
struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent,
|
||||
JumphitData data) {
|
||||
struct mango_text_node *node = calloc(1, sizeof(*node));
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
node->scene_buffer = wlr_scene_buffer_create(parent, NULL);
|
||||
if (!node->scene_buffer) {
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
node->scene_buffer = wlr_scene_buffer_create(parent, NULL);
|
||||
if (!node->scene_buffer) {
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mango_text_node_update(struct mango_text_node *node, const char *text,
|
||||
const char *font_desc, float color[4], float scale) {
|
||||
if (!node || !text || !font_desc) return;
|
||||
if (scale <= 0.0f) scale = 1.0f;
|
||||
// 默认外观:无边框、无背景、无内边距(保持原有行为)
|
||||
node->fg_color[0] = data.fg_color[0];
|
||||
node->fg_color[1] = data.fg_color[1];
|
||||
node->fg_color[2] = data.fg_color[2];
|
||||
node->fg_color[3] = data.fg_color[3];
|
||||
node->bg_color[0] = data.bg_color[0];
|
||||
node->bg_color[1] = data.bg_color[1];
|
||||
node->bg_color[2] = data.bg_color[2];
|
||||
node->bg_color[3] = data.bg_color[3];
|
||||
node->border_color[0] = data.border_color[0];
|
||||
node->border_color[1] = data.border_color[1];
|
||||
node->border_color[2] = data.border_color[2];
|
||||
node->border_color[3] = data.border_color[3];
|
||||
node->border_width = data.border_width;
|
||||
node->corner_radius = data.corner_radius;
|
||||
node->padding_x = data.padding_x;
|
||||
node->padding_y = data.padding_y;
|
||||
node->font_desc = data.font_desc ? data.font_desc : "monospace Bold 24";
|
||||
|
||||
// 测量文字像素尺寸
|
||||
cairo_surface_t *dummy_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
|
||||
cairo_t *dummy_cr = cairo_create(dummy_surface);
|
||||
PangoContext *dummy_ctx = pango_cairo_create_context(dummy_cr);
|
||||
pango_cairo_context_set_resolution(dummy_ctx, 96.0 * scale);
|
||||
PangoLayout *dummy_layout = pango_layout_new(dummy_ctx);
|
||||
PangoFontDescription *desc = pango_font_description_from_string(font_desc);
|
||||
pango_layout_set_font_description(dummy_layout, desc);
|
||||
pango_layout_set_text(dummy_layout, text, -1);
|
||||
|
||||
int pixel_width, pixel_height;
|
||||
pango_layout_get_pixel_size(dummy_layout, &pixel_width, &pixel_height);
|
||||
|
||||
g_object_unref(dummy_layout);
|
||||
pango_font_description_free(desc);
|
||||
g_object_unref(dummy_ctx);
|
||||
cairo_destroy(dummy_cr);
|
||||
cairo_surface_destroy(dummy_surface);
|
||||
|
||||
if (pixel_width <= 0 || pixel_height <= 0) {
|
||||
wlr_scene_buffer_set_buffer(node->scene_buffer, NULL);
|
||||
node->logical_width = 0;
|
||||
node->logical_height = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建真实大小的 Surface 并绘制文字
|
||||
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pixel_width, pixel_height);
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
|
||||
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_paint(cr);
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
||||
|
||||
PangoContext *ctx = pango_cairo_create_context(cr);
|
||||
pango_cairo_context_set_resolution(ctx, 96.0 * scale);
|
||||
PangoLayout *layout = pango_layout_new(ctx);
|
||||
desc = pango_font_description_from_string(font_desc);
|
||||
pango_layout_set_font_description(layout, desc);
|
||||
pango_layout_set_text(layout, text, -1);
|
||||
|
||||
cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]);
|
||||
pango_cairo_show_layout(cr, layout);
|
||||
cairo_surface_flush(surface);
|
||||
|
||||
// 包装成 wlr_buffer
|
||||
struct mango_text_buffer *buf = calloc(1, sizeof(*buf));
|
||||
if (!buf) {
|
||||
cairo_surface_destroy(surface);
|
||||
cairo_destroy(cr);
|
||||
g_object_unref(layout);
|
||||
pango_font_description_free(desc);
|
||||
g_object_unref(ctx);
|
||||
return;
|
||||
}
|
||||
wlr_buffer_init(&buf->base, &text_buffer_impl, pixel_width, pixel_height);
|
||||
buf->surface = surface;
|
||||
|
||||
// 提交给场景图
|
||||
wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base);
|
||||
wlr_buffer_drop(&buf->base); // 函数内引用计数 -1,交给场景图管理
|
||||
|
||||
// 设置逻辑大小,实现 HiDPI 缩放
|
||||
node->logical_width = (int)(pixel_width / scale);
|
||||
node->logical_height = (int)(pixel_height / scale);
|
||||
wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, node->logical_height);
|
||||
|
||||
// 清理绘制资源
|
||||
g_object_unref(layout);
|
||||
pango_font_description_free(desc);
|
||||
g_object_unref(ctx);
|
||||
cairo_destroy(cr);
|
||||
return node;
|
||||
}
|
||||
|
||||
void mango_text_node_destroy(struct mango_text_node *node) {
|
||||
if (!node) return;
|
||||
wlr_scene_node_destroy(&node->scene_buffer->node);
|
||||
free(node);
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
wlr_scene_node_destroy(&node->scene_buffer->node);
|
||||
free(node);
|
||||
}
|
||||
|
||||
void mango_text_node_set_background(struct mango_text_node *node, float r,
|
||||
float g, float b, float a) {
|
||||
if (!node)
|
||||
return;
|
||||
node->bg_color[0] = r;
|
||||
node->bg_color[1] = g;
|
||||
node->bg_color[2] = b;
|
||||
node->bg_color[3] = a;
|
||||
}
|
||||
|
||||
void mango_text_node_set_border(struct mango_text_node *node, float r, float g,
|
||||
float b, float a, float width, float radius) {
|
||||
if (!node)
|
||||
return;
|
||||
node->border_color[0] = r;
|
||||
node->border_color[1] = g;
|
||||
node->border_color[2] = b;
|
||||
node->border_color[3] = a;
|
||||
node->border_width = width > 0.0f ? width : 0.0f;
|
||||
node->corner_radius = radius >= 0.0f ? radius : 0.0f;
|
||||
}
|
||||
|
||||
void mango_text_node_set_padding(struct mango_text_node *node, float pad_x,
|
||||
float pad_y) {
|
||||
if (!node)
|
||||
return;
|
||||
node->padding_x = pad_x >= 0.0f ? pad_x : 0.0f;
|
||||
node->padding_y = pad_y >= 0.0f ? pad_y : 0.0f;
|
||||
}
|
||||
|
||||
static void draw_rounded_rect(cairo_t *cr, double x, double y, double w,
|
||||
double h, double r) {
|
||||
// 使用 Cairo 的标准圆角矩形路径
|
||||
double degrees = G_PI / 180.0;
|
||||
cairo_new_sub_path(cr);
|
||||
cairo_arc(cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees);
|
||||
cairo_arc(cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees);
|
||||
cairo_arc(cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees);
|
||||
cairo_arc(cr, x + r, y + r, r, 180 * degrees, 270 * degrees);
|
||||
cairo_close_path(cr);
|
||||
}
|
||||
|
||||
void mango_text_node_update(struct mango_text_node *node, const char *text,
|
||||
|
||||
float scale) {
|
||||
if (!node || !text)
|
||||
return;
|
||||
if (scale <= 0.0f)
|
||||
scale = 1.0f;
|
||||
|
||||
const char *font_desc = node->font_desc;
|
||||
PangoFontDescription *desc = get_cached_font_desc(font_desc);
|
||||
|
||||
// 测量文字像素尺寸(无缩放的实际像素)
|
||||
cairo_surface_t *dummy_surface =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
|
||||
cairo_t *dummy_cr = cairo_create(dummy_surface);
|
||||
PangoContext *dummy_ctx = pango_cairo_create_context(dummy_cr);
|
||||
pango_cairo_context_set_resolution(dummy_ctx, 96.0 * scale);
|
||||
PangoLayout *dummy_layout = pango_layout_new(dummy_ctx);
|
||||
pango_layout_set_font_description(dummy_layout, desc);
|
||||
pango_layout_set_text(dummy_layout, text, -1);
|
||||
|
||||
int text_pixel_w, text_pixel_h;
|
||||
pango_layout_get_pixel_size(dummy_layout, &text_pixel_w, &text_pixel_h);
|
||||
|
||||
g_object_unref(dummy_layout);
|
||||
g_object_unref(dummy_ctx);
|
||||
cairo_destroy(dummy_cr);
|
||||
cairo_surface_destroy(dummy_surface);
|
||||
|
||||
if (text_pixel_w <= 0 || text_pixel_h <= 0) {
|
||||
wlr_scene_buffer_set_buffer(node->scene_buffer, NULL);
|
||||
node->logical_width = 0;
|
||||
node->logical_height = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 逻辑尺寸:文本的逻辑宽高
|
||||
float logical_text_w = text_pixel_w / scale;
|
||||
float logical_text_h = text_pixel_h / scale;
|
||||
|
||||
// 背景框的逻辑尺寸(包含内边距)
|
||||
float pad_x = node->padding_x;
|
||||
float pad_y = node->padding_y;
|
||||
float box_logical_w = logical_text_w + 2.0f * pad_x;
|
||||
float box_logical_h = logical_text_h + 2.0f * pad_y;
|
||||
|
||||
// 边框相关尺寸(全部以逻辑像素为单位)
|
||||
float border = node->border_width;
|
||||
bool draw_border = (border > 0.0f) && (node->border_color[3] > 0.0f);
|
||||
bool draw_bg = (node->bg_color[3] > 0.0f);
|
||||
|
||||
// 最终 surface 的像素尺寸:背景框 + 两倍边框宽度(边框外扩)
|
||||
int surface_pixel_w = (int)((box_logical_w + 2.0f * border) * scale + 0.5f);
|
||||
int surface_pixel_h = (int)((box_logical_h + 2.0f * border) * scale + 0.5f);
|
||||
|
||||
// 限制最小尺寸以避免 Cairo 错误
|
||||
if (surface_pixel_w < 1)
|
||||
surface_pixel_w = 1;
|
||||
if (surface_pixel_h < 1)
|
||||
surface_pixel_h = 1;
|
||||
|
||||
// 创建绘制表面
|
||||
cairo_surface_t *surface = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_ARGB32, surface_pixel_w, surface_pixel_h);
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
|
||||
// 清空为全透明
|
||||
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_paint(cr);
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
||||
|
||||
// --- 计算关键坐标(像素) ---
|
||||
double bg_x = border * scale;
|
||||
double bg_y = border * scale;
|
||||
double bg_w = box_logical_w * scale;
|
||||
double bg_h = box_logical_h * scale;
|
||||
|
||||
double radius = node->corner_radius * scale;
|
||||
// 如果 radius <
|
||||
// 0,表示自动使用半宽半高形成胶囊形状(备选,这里保持简单,默认0不圆角)
|
||||
if (radius < 0) {
|
||||
radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0;
|
||||
}
|
||||
if (radius > bg_w / 2.0)
|
||||
radius = bg_w / 2.0;
|
||||
if (radius > bg_h / 2.0)
|
||||
radius = bg_h / 2.0;
|
||||
|
||||
// 1. 绘制背景
|
||||
if (draw_bg) {
|
||||
cairo_set_source_rgba(cr, node->bg_color[0], node->bg_color[1],
|
||||
node->bg_color[2], node->bg_color[3]);
|
||||
if (radius > 0.0) {
|
||||
draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius);
|
||||
cairo_fill(cr);
|
||||
} else {
|
||||
cairo_rectangle(cr, bg_x, bg_y, bg_w, bg_h);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 绘制文本(偏移 = 边框 + 内边距)
|
||||
cairo_save(cr);
|
||||
cairo_translate(cr, (border + pad_x) * scale, (border + pad_y) * scale);
|
||||
|
||||
PangoContext *ctx = pango_cairo_create_context(cr);
|
||||
pango_cairo_context_set_resolution(ctx, 96.0 * scale);
|
||||
PangoLayout *layout = pango_layout_new(ctx);
|
||||
pango_layout_set_font_description(layout, desc);
|
||||
pango_layout_set_text(layout, text, -1);
|
||||
|
||||
cairo_set_source_rgba(cr, node->fg_color[0], node->fg_color[1],
|
||||
node->fg_color[2], node->fg_color[3]);
|
||||
pango_cairo_show_layout(cr, layout);
|
||||
|
||||
g_object_unref(layout);
|
||||
g_object_unref(ctx);
|
||||
cairo_restore(cr); // 回到原坐标,方便画边框
|
||||
|
||||
// 3. 绘制边框
|
||||
if (draw_border) {
|
||||
cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1],
|
||||
node->border_color[2], node->border_color[3]);
|
||||
cairo_set_line_width(cr, border * scale);
|
||||
|
||||
// 边框路径:比背景框外扩半个线宽,使得边框外缘恰好贴合 surface 边缘
|
||||
double half_lw = border * scale * 0.5;
|
||||
double bx = bg_x - half_lw;
|
||||
double by = bg_y - half_lw;
|
||||
double bw = bg_w + border * scale;
|
||||
double bh = bg_h + border * scale;
|
||||
|
||||
if (radius > 0.0) {
|
||||
// 圆角边框同样外扩半径(半径也需增加 half_lw)
|
||||
double outer_radius = radius + half_lw;
|
||||
if (outer_radius < 0.0)
|
||||
outer_radius = 0.0;
|
||||
draw_rounded_rect(cr, bx, by, bw, bh, outer_radius);
|
||||
cairo_stroke(cr);
|
||||
} else {
|
||||
cairo_rectangle(cr, bx, by, bw, bh);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
}
|
||||
|
||||
cairo_surface_flush(surface);
|
||||
|
||||
// 包装成 wlr_buffer
|
||||
struct mango_text_buffer *buf = calloc(1, sizeof(*buf));
|
||||
if (!buf) {
|
||||
cairo_surface_destroy(surface);
|
||||
cairo_destroy(cr);
|
||||
return;
|
||||
}
|
||||
wlr_buffer_init(&buf->base, &text_buffer_impl, surface_pixel_w,
|
||||
surface_pixel_h);
|
||||
buf->surface = surface;
|
||||
|
||||
wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base);
|
||||
wlr_buffer_drop(&buf->base);
|
||||
|
||||
// 最终逻辑大小 = 背景框逻辑尺寸 + 边框逻辑尺寸
|
||||
node->logical_width = (int)(box_logical_w + 2.0f * border);
|
||||
node->logical_height = (int)(box_logical_h + 2.0f * border);
|
||||
wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width,
|
||||
node->logical_height);
|
||||
|
||||
cairo_destroy(cr);
|
||||
}
|
||||
|
|
@ -1,34 +1,52 @@
|
|||
#ifndef MANGO_TEXT_H
|
||||
#define MANGO_TEXT_H
|
||||
#ifndef MANGO_TEXT_NODE_H
|
||||
#define MANGO_TEXT_NODE_H
|
||||
|
||||
#include <wayland-server-core.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
|
||||
struct mango_text_node {
|
||||
struct wlr_scene_buffer *scene_buffer;
|
||||
int logical_width;
|
||||
int logical_height;
|
||||
struct wlr_scene_buffer *scene_buffer;
|
||||
int logical_width;
|
||||
int logical_height;
|
||||
const char *font_desc;
|
||||
|
||||
// 外观属性
|
||||
float fg_color[4]; // 文本颜色 RGBA,默认白色
|
||||
float bg_color[4]; // 背景色 RGBA,默认透明
|
||||
float border_color[4]; // 边框色 RGBA,默认透明
|
||||
int32_t border_width; // 边框宽度(逻辑像素),<=0 则不绘制
|
||||
int32_t corner_radius; // 圆角半径(逻辑像素),<0 时自动取
|
||||
// min(width,height)/2 形成胶囊
|
||||
int32_t padding_x; // 文本左右内边距(逻辑像素)
|
||||
int32_t padding_y; // 文本上下内边距(逻辑像素)
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建一个独立的文字场景节点
|
||||
* @param parent 父级场景树节点(例如窗口装饰树、OSD层树或状态栏树)
|
||||
*/
|
||||
struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent);
|
||||
typedef struct {
|
||||
float fg_color[4];
|
||||
float bg_color[4];
|
||||
float border_color[4];
|
||||
int32_t border_width;
|
||||
int32_t corner_radius;
|
||||
int32_t padding_x;
|
||||
int32_t padding_y;
|
||||
const char *font_desc;
|
||||
} JumphitData;
|
||||
|
||||
/**
|
||||
* 更新节点中的文字内容、字体、颜色和缩放
|
||||
* @param node 文字节点指针
|
||||
* @param text 要显示的文本内容
|
||||
* @param font_desc Pango 字体描述字符串 (例如 "Sans 12" 或 "JetBrains Mono Bold 14")
|
||||
* @param color RGBA 颜色数组,范围 0.0 ~ 1.0
|
||||
* @param scale 当前输出设备的缩放比例 (HiDPI 支持)
|
||||
*/
|
||||
void mango_text_node_update(struct mango_text_node *node, const char *text,
|
||||
const char *font_desc, float color[4], float scale);
|
||||
|
||||
/**
|
||||
* 销毁文字节点并释放相关场景图资源
|
||||
*/
|
||||
struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent,
|
||||
JumphitData data);
|
||||
void mango_text_node_destroy(struct mango_text_node *node);
|
||||
void mango_text_node_update(struct mango_text_node *node, const char *text,
|
||||
float scale);
|
||||
|
||||
#endif /* MANGO_TEXT_H */
|
||||
// 外观设置接口
|
||||
void mango_text_node_set_background(struct mango_text_node *node, float r,
|
||||
float g, float b, float a);
|
||||
void mango_text_node_set_border(struct mango_text_node *node, float r, float g,
|
||||
float b, float a, float width, float radius);
|
||||
void mango_text_node_set_padding(struct mango_text_node *node, float pad_x,
|
||||
float pad_y);
|
||||
|
||||
void mango_text_node_destroy(struct mango_text_node *node);
|
||||
void mango_text_global_finish(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -353,10 +353,65 @@ void overview_resize(Monitor *m) {
|
|||
free(c_arr);
|
||||
}
|
||||
|
||||
void create_jump_hints(Monitor *m) {
|
||||
const char jump_labels[] = "HJKLASDFGQWERTYUIOPZXCVBNM";
|
||||
int label_idx = 0;
|
||||
Client *c;
|
||||
|
||||
wl_list_for_each(c, &clients, link) {
|
||||
if (VISIBLEON(c, m) &&
|
||||
((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) {
|
||||
if (label_idx >= 26)
|
||||
break;
|
||||
char c_char = jump_labels[label_idx];
|
||||
c->jump_char = c_char;
|
||||
|
||||
// 把字符变成字符串
|
||||
char label_text[2] = {c_char, '\0'};
|
||||
|
||||
mango_text_node_update(c->text_node, label_text, 1.0f);
|
||||
wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, true);
|
||||
wlr_scene_node_raise_to_top(&c->text_node->scene_buffer->node);
|
||||
wlr_scene_node_set_position(
|
||||
&c->text_node->scene_buffer->node,
|
||||
c->geom.width / 2 - c->text_node->logical_width / 2,
|
||||
c->geom.height / 2 - c->text_node->logical_height / 2);
|
||||
label_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void begin_jump_mode(Monitor *m) { m->is_jump_mode = 1; }
|
||||
|
||||
void finish_jump_mode(Monitor *m) {
|
||||
Client *c = NULL;
|
||||
|
||||
if (!m->is_jump_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
wl_list_for_each(c, &clients, link) {
|
||||
if (VISIBLEON(c, m)) {
|
||||
if (c->text_node->scene_buffer->node.enabled) {
|
||||
c->jump_char = '\0';
|
||||
wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m->is_jump_mode = 0;
|
||||
}
|
||||
|
||||
void overview(Monitor *m) {
|
||||
|
||||
if (config.ov_no_resize) {
|
||||
overview_scale(m);
|
||||
} else {
|
||||
overview_resize(m);
|
||||
}
|
||||
|
||||
if (m->is_jump_mode) {
|
||||
create_jump_hints(m);
|
||||
}
|
||||
}
|
||||
33
src/mango.c
33
src/mango.c
|
|
@ -435,6 +435,7 @@ struct Client {
|
|||
int32_t allow_shortcuts_inhibit;
|
||||
float scroller_proportion_single;
|
||||
bool isfocusing;
|
||||
char jump_char;
|
||||
bool enable_drop_area_draw;
|
||||
int32_t drop_direction;
|
||||
struct wlr_box drag_tile_float_backup_geom;
|
||||
|
|
@ -556,6 +557,7 @@ struct Monitor {
|
|||
uint32_t ovbk_prev_tagset;
|
||||
Client *sel, *prevsel;
|
||||
int32_t isoverview;
|
||||
int32_t is_jump_mode;
|
||||
int32_t is_in_hotarea;
|
||||
int32_t asleep;
|
||||
uint32_t visible_clients;
|
||||
|
|
@ -901,6 +903,10 @@ Client *scroll_get_stack_tail_client(Client *c);
|
|||
static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c);
|
||||
static void overview_backup_surface(Client *c);
|
||||
|
||||
static void create_jump_hints(Monitor *m);
|
||||
static void finish_jump_mode(Monitor *m);
|
||||
static void begin_jump_mode(Monitor *m);
|
||||
|
||||
#include "data/static_keymap.h"
|
||||
#include "dispatch/bind_declare.h"
|
||||
#include "layout/layout.h"
|
||||
|
|
@ -2563,6 +2569,8 @@ void cleanup(void) {
|
|||
/* Destroy after the wayland display (when the monitors are already
|
||||
destroyed) to avoid destroying them with an invalid scene output. */
|
||||
wlr_scene_node_destroy(&scene->tree.node);
|
||||
|
||||
mango_text_global_finish();
|
||||
}
|
||||
|
||||
void cleanupmon(struct wl_listener *listener, void *data) {
|
||||
|
|
@ -4256,6 +4264,27 @@ void keypress(struct wl_listener *listener, void *data) {
|
|||
if (handled)
|
||||
return;
|
||||
|
||||
if (selmon && selmon->is_jump_mode &&
|
||||
event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
|
||||
for (i = 0; i < nsyms; i++) {
|
||||
xkb_keysym_t sym = xkb_keysym_to_lower(syms[i]);
|
||||
if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) {
|
||||
char c_char = 'A' + (sym - XKB_KEY_a);
|
||||
Client *c;
|
||||
wl_list_for_each(c, &clients, link) {
|
||||
if (c->mon == selmon && c->jump_char == c_char) {
|
||||
focusclient(c, 1);
|
||||
toggleoverview(&(Arg){.i = 1});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (sym == XKB_KEY_Escape) {
|
||||
togglejump(&(Arg){.i = 0});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* don't pass when popup is focused
|
||||
* this is better than having popups (like fuzzel or wmenu) closing
|
||||
* while typing in a passed keybind */
|
||||
|
|
@ -4515,8 +4544,7 @@ mapnotify(struct wl_listener *listener, void *data) {
|
|||
#endif
|
||||
// extra node
|
||||
|
||||
c->text_node = mango_text_node_create(c->scene);
|
||||
mango_text_node_update(c->text_node, "hello world", "Sans 50", config.focuscolor, 1.0f);
|
||||
c->text_node = mango_text_node_create(c->scene, config.jumhitdata);
|
||||
wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node);
|
||||
wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false);
|
||||
|
||||
|
|
@ -6517,6 +6545,7 @@ void unmapnotify(struct wl_listener *listener, void *data) {
|
|||
|
||||
c->stack_proportion = 0.0f;
|
||||
|
||||
mango_text_node_destroy(c->text_node);
|
||||
wlr_scene_node_destroy(&c->scene->node);
|
||||
printstatus(IPC_WATCH_ARRANGGE);
|
||||
motionnotify(0, NULL, 0, 0, 0, 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue