From 9e2f0fc2b86adbda938073d620aec436677db1f2 Mon Sep 17 00:00:00 2001 From: ernestoCruz05 Date: Sat, 21 Mar 2026 14:20:55 +0000 Subject: [PATCH] feat: add canvas layout --- assets/config.conf | 7 +- docs/installation.md | 2 +- meson.build | 2 +- src/animation/client.h | 260 +++++++--- src/config/parse_config.h | 42 +- src/dispatch/bind_declare.h | 4 + src/dispatch/bind_define.h | 142 +++++- src/fetch/common.h | 11 +- src/fetch/monitor.h | 4 + src/layout/arrange.h | 31 +- src/layout/canvas.h | 146 ++++++ src/layout/layout.h | 3 + src/mango.c | 982 ++++++++++++++++++++++++++++++++++-- 13 files changed, 1509 insertions(+), 127 deletions(-) create mode 100644 src/layout/canvas.h diff --git a/assets/config.conf b/assets/config.conf index eb326d16..414ff79d 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -174,10 +174,15 @@ bind=ALT,a,togglemaximizescreen, bind=ALT,f,togglefullscreen, bind=ALT+SHIFT,f,togglefakefullscreen, bind=SUPER,i,minimized, -bind=SUPER,o,toggleoverlay, bind=SUPER+SHIFT,I,restore_minimized bind=ALT,z,toggle_scratchpad +# canvas layout +bind=SUPER,o,toggleminimap, +bind=SUPER,p,canvas_overview_toggle, +bind=SUPER,z,canvas_zoom_resize,0.909090909 +bind=SUPER,x,canvas_zoom_resize,1.1 + # scroller layout bind=ALT,e,set_proportion,1.0 bind=ALT,x,switch_proportion_preset, diff --git a/docs/installation.md b/docs/installation.md index d3b9afaa..6f3927a0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -185,7 +185,7 @@ mangowm is available in the **PikaOS package repository**. You can install it using the `pikman` package manager: ```bash -pikman install mangowc +pikman install mangowm ``` --- diff --git a/meson.build b/meson.build index 5d09d536..64394cd9 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.7', + version : '0.12.8', ) subdir('protocols') diff --git a/src/animation/client.h b/src/animation/client.h index e94f872a..f2afa106 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -161,21 +161,6 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, int32_t sy, void *data) { BufferData *buffer_data = (BufferData *)data; - if (buffer_data->should_scale && buffer_data->height_scale < 1 && - buffer_data->width_scale < 1) { - buffer_data->should_scale = false; - } - - if (buffer_data->should_scale && buffer_data->height_scale == 1 && - buffer_data->width_scale < 1) { - buffer_data->should_scale = false; - } - - if (buffer_data->should_scale && buffer_data->height_scale < 1 && - buffer_data->width_scale == 1) { - buffer_data->should_scale = false; - } - struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); @@ -189,12 +174,8 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, int32_t surface_width = surface->current.width; int32_t surface_height = surface->current.height; - surface_width = buffer_data->width_scale < 1 - ? surface_width - : buffer_data->width_scale * surface_width; - surface_height = buffer_data->height_scale < 1 - ? surface_height - : buffer_data->height_scale * surface_height; + surface_width = buffer_data->width_scale * surface_width; + surface_height = buffer_data->height_scale * surface_height; if (surface_width > buffer_data->width && wlr_subsurface_try_from_wlr_surface(surface) == NULL) { @@ -230,6 +211,87 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, buffer_data->corner_location); } +static void scene_buffer_apply_canvas_zoom(struct wlr_scene_buffer *buffer, + int32_t sx, int32_t sy, void *data) { + float zoom = *(float *)data; + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(buffer); + if (!scene_surface) + return; + + wlr_scene_node_set_position(&buffer->node, + (int32_t)roundf(buffer->node.x * zoom), + (int32_t)roundf(buffer->node.y * zoom)); + + int32_t w = buffer->dst_width > 0 ? buffer->dst_width + : scene_surface->surface->current.width; + int32_t h = buffer->dst_height > 0 ? buffer->dst_height + : scene_surface->surface->current.height; + wlr_scene_buffer_set_dest_size(buffer, (int32_t)roundf(w * zoom), + (int32_t)roundf(h * zoom)); +} + +static void apply_canvas_zoom_correct(Client *c, float zoom) { + if (!c || !c->scene) + return; + wlr_scene_node_for_each_buffer(&c->scene_surface->node, + scene_buffer_apply_canvas_zoom, &zoom); +} + +static void scene_buffer_apply_zoom(struct wlr_scene_buffer *buffer, int32_t sx, + int32_t sy, void *data) { + float zoom = *(float *)data; + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(buffer); + if (!scene_surface) + return; + + int32_t w = scene_surface->surface->current.width; + int32_t h = scene_surface->surface->current.height; + wlr_scene_buffer_set_dest_size(buffer, (int32_t)roundf(w * zoom), + (int32_t)roundf(h * zoom)); +} + +static void scene_buffer_clear_dest_size(struct wlr_scene_buffer *buffer, + int32_t sx, int32_t sy, void *data) { + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(buffer); + if (!scene_surface) + return; + wlr_scene_buffer_set_dest_size(buffer, 0, 0); +} + +static void clear_visual_zoom(Client *c) { + if (!c || !c->scene) + return; + wlr_scene_node_for_each_buffer(&c->scene_surface->node, + scene_buffer_clear_dest_size, NULL); +} + +static void apply_visual_zoom(Client *c, float zoom) { + if (!c || !c->scene || zoom == 1.0f) + return; + + wlr_scene_node_for_each_buffer(&c->scene_surface->node, + scene_buffer_apply_zoom, &zoom); +} + +static float get_client_effective_zoom(Client *c) { + if (c->mon && is_canvas_layout(c->mon) && !c->isfullscreen && + !c->ismaximizescreen) { + uint32_t tag = c->mon->pertag->curtag; + float zoom = c->mon->pertag->canvas_zoom[tag]; + float effective_zoom = zoom; + if (c->mon->canvas_in_overview && + c->canvas_geom_backup[tag].width > 0) { + effective_zoom *= (float)c->canvas_geom[tag].width / + c->canvas_geom_backup[tag].width; + } + return effective_zoom; + } + return 1.0f; +} + void buffer_set_effect(Client *c, BufferData data) { if (!c || c->iskilling) @@ -267,6 +329,8 @@ void client_draw_shadow(Client *c) { wlr_scene_node_set_enabled(&c->shadow->node, true); } + float zoom = get_client_effective_zoom(c); + bool hit_no_border = check_hit_no_border(c); enum corner_location current_corner_location = c->isfullscreen || (config.no_radius_when_single && c->mon && @@ -307,10 +371,10 @@ void client_draw_shadow(Client *c) { }; struct wlr_box absolute_shadow_box = { - .x = shadow_box.x + c->animation.current.x, - .y = shadow_box.y + c->animation.current.y, - .width = shadow_box.width, - .height = shadow_box.height, + .x = shadow_box.x * zoom + c->animation.current.x, + .y = shadow_box.y * zoom + c->animation.current.y, + .width = shadow_box.width * zoom, + .height = shadow_box.height * zoom, }; int32_t right_offset, bottom_offset, left_offset, top_offset; @@ -323,13 +387,15 @@ void client_draw_shadow(Client *c) { } else { right_offset = GEZERO(absolute_shadow_box.x + absolute_shadow_box.width - - c->mon->m.x - c->mon->m.width); + c->mon->m.x - c->mon->m.width) / + zoom; bottom_offset = GEZERO(absolute_shadow_box.y + absolute_shadow_box.height - - c->mon->m.y - c->mon->m.height); + c->mon->m.y - c->mon->m.height) / + zoom; - left_offset = GEZERO(c->mon->m.x - absolute_shadow_box.x); - top_offset = GEZERO(c->mon->m.y - absolute_shadow_box.y); + left_offset = GEZERO(c->mon->m.x - absolute_shadow_box.x) / zoom; + top_offset = GEZERO(c->mon->m.y - absolute_shadow_box.y) / zoom; } left_offset = MIN(left_offset, shadow_box.width); @@ -337,15 +403,18 @@ void client_draw_shadow(Client *c) { top_offset = MIN(top_offset, shadow_box.height); bottom_offset = MIN(bottom_offset, shadow_box.height); - wlr_scene_node_set_position(&c->shadow->node, shadow_box.x + left_offset, - shadow_box.y + top_offset); + wlr_scene_node_set_position(&c->shadow->node, + (shadow_box.x + left_offset) * zoom, + (shadow_box.y + top_offset) * zoom); wlr_scene_shadow_set_size( - c->shadow, GEZERO(shadow_box.width - left_offset - right_offset), - GEZERO(shadow_box.height - top_offset - bottom_offset)); + c->shadow, GEZERO(shadow_box.width - left_offset - right_offset) * zoom, + GEZERO(shadow_box.height - top_offset - bottom_offset) * zoom); - clipped_region.area.x = clipped_region.area.x - left_offset; - clipped_region.area.y = clipped_region.area.y - top_offset; + clipped_region.area.x = (clipped_region.area.x - left_offset) * zoom; + clipped_region.area.y = (clipped_region.area.y - top_offset) * zoom; + clipped_region.area.width *= zoom; + clipped_region.area.height *= zoom; wlr_scene_shadow_set_clipped_region(c->shadow, clipped_region); } @@ -354,6 +423,8 @@ void apply_border(Client *c) { if (!c || c->iskilling || !client_surface(c)->mapped) return; + float zoom = get_client_effective_zoom(c); + bool hit_no_border = check_hit_no_border(c); enum corner_location current_corner_location; if (c->isfullscreen || (config.no_radius_when_single && c->mon && @@ -389,14 +460,16 @@ void apply_border(Client *c) { top_offset = 0; } else { right_offset = - GEZERO(c->animation.current.x + c->animation.current.width - - c->mon->m.x - c->mon->m.width); + GEZERO(c->animation.current.x + c->animation.current.width * zoom - + c->mon->m.x - c->mon->m.width) / + zoom; bottom_offset = - GEZERO(c->animation.current.y + c->animation.current.height - - c->mon->m.y - c->mon->m.height); + GEZERO(c->animation.current.y + c->animation.current.height * zoom - + c->mon->m.y - c->mon->m.height) / + zoom; - left_offset = GEZERO(c->mon->m.x - c->animation.current.x); - top_offset = GEZERO(c->mon->m.y - c->animation.current.y); + left_offset = GEZERO(c->mon->m.x - c->animation.current.x) / zoom; + top_offset = GEZERO(c->mon->m.y - c->animation.current.y) / zoom; } int32_t inner_surface_width = GEZERO(clip_box.width - 2 * bw); @@ -438,9 +511,14 @@ void apply_border(Client *c) { .corners = current_corner_location, }; - wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); - wlr_scene_rect_set_size(c->border, rect_width, rect_height); - wlr_scene_node_set_position(&c->border->node, rect_x, rect_y); + wlr_scene_node_set_position(&c->scene_surface->node, + (int32_t)roundf(c->bw * zoom), + (int32_t)roundf(c->bw * zoom)); + wlr_scene_rect_set_size(c->border, (int32_t)roundf(rect_width * zoom), + (int32_t)roundf(rect_height * zoom)); + wlr_scene_node_set_position(&c->border->node, + (int32_t)roundf(rect_x * zoom), + (int32_t)roundf(rect_y * zoom)); wlr_scene_rect_set_corner_radius(c->border, config.border_radius, current_corner_location); wlr_scene_rect_set_clipped_region(c->border, clipped_region); @@ -450,18 +528,25 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { int32_t offsetx = 0, offsety = 0, offsetw = 0, offseth = 0; struct ivec2 offset = {0, 0, 0, 0}; - if (!ISSCROLLTILED(c) && !c->animation.tagining && !c->animation.tagouted && + if (!ISSCROLLTILED(c) && !(c->mon && is_canvas_layout(c->mon)) && + !c->animation.tagining && !c->animation.tagouted && !c->animation.tagouting) return offset; + float zoom = get_client_effective_zoom(c); + int32_t bottom_out_offset = - GEZERO(c->animation.current.y + c->animation.current.height - - c->mon->m.y - c->mon->m.height); + GEZERO(c->animation.current.y + c->animation.current.height * zoom - + c->mon->m.y - c->mon->m.height) / + zoom; int32_t right_out_offset = - GEZERO(c->animation.current.x + c->animation.current.width - - c->mon->m.x - c->mon->m.width); - int32_t left_out_offset = GEZERO(c->mon->m.x - c->animation.current.x); - int32_t top_out_offset = GEZERO(c->mon->m.y - c->animation.current.y); + GEZERO(c->animation.current.x + c->animation.current.width * zoom - + c->mon->m.x - c->mon->m.width) / + zoom; + int32_t left_out_offset = + GEZERO(c->mon->m.x - c->animation.current.x) / zoom; + int32_t top_out_offset = + GEZERO(c->mon->m.y - c->animation.current.y) / zoom; // 必须转换为int,否计算会没有负数导致判断错误 int32_t bw = (int32_t)c->bw; @@ -471,7 +556,8 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { 需要主要border超出屏幕的时候不计算如偏差之内而是 要等窗口表面超出才开始计算偏差 */ - if (ISSCROLLTILED(c) || c->animation.tagining || c->animation.tagouted || + if (ISSCROLLTILED(c) || (c->mon && is_canvas_layout(c->mon)) || + c->animation.tagining || c->animation.tagouted || c->animation.tagouting) { if (left_out_offset > 0) { offsetx = GEZERO(left_out_offset - bw); @@ -499,7 +585,8 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { offset.height = offseth; if ((clip_box->width + bw <= 0 || clip_box->height + bw <= 0) && - (ISSCROLLTILED(c) || c->animation.tagouting || c->animation.tagining)) { + (ISSCROLLTILED(c) || (c->mon && is_canvas_layout(c->mon)) || + c->animation.tagouting || c->animation.tagining)) { c->is_clip_to_hide = true; wlr_scene_node_set_enabled(&c->scene->node, false); } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon)) { @@ -540,6 +627,17 @@ void client_apply_clip(Client *c, float factor) { return; } + if (!(c->mon && is_canvas_layout(c->mon))) { + float noanim_zoom = get_client_effective_zoom(c); + if (noanim_zoom != 1.0f) { + clip_box.x = (int32_t)roundf(clip_box.x * noanim_zoom); + clip_box.y = (int32_t)roundf(clip_box.y * noanim_zoom); + clip_box.width = (int32_t)roundf(clip_box.width * noanim_zoom); + clip_box.height = + (int32_t)roundf(clip_box.height * noanim_zoom); + } + } + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); buffer_set_effect(c, (BufferData){1.0f, 1.0f, clip_box.width, clip_box.height, @@ -587,6 +685,16 @@ void client_apply_clip(Client *c, float factor) { return; } + if (!(c->mon && is_canvas_layout(c->mon))) { + float clip_zoom = get_client_effective_zoom(c); + if (clip_zoom != 1.0f) { + clip_box.x = (int32_t)roundf(clip_box.x * clip_zoom); + clip_box.y = (int32_t)roundf(clip_box.y * clip_zoom); + clip_box.width = (int32_t)roundf(clip_box.width * clip_zoom); + clip_box.height = (int32_t)roundf(clip_box.height * clip_zoom); + } + } + // 应用窗口表面剪切 wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); @@ -926,7 +1034,8 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { // float_geom = c->geom; bbox = (interact || c->isfloating || c->isfullscreen) ? &sgeom : &c->mon->w; - if (is_scroller_layout(c->mon) && (!c->isfloating || c == grabc)) { + if (is_canvas_layout(c->mon) || + (is_scroller_layout(c->mon) && (!c->isfloating || c == grabc))) { c->geom = geo; c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); @@ -1182,20 +1291,41 @@ bool client_draw_frame(Client *c) { if (!c || !client_surface(c)->mapped) return false; - if (!c->need_output_flush) { + bool need_flush = c->need_output_flush; + + if (need_flush) { + if (config.animations && c->animation.running) { + client_animation_next_tick(c); + } else { + wlr_scene_node_set_position(&c->scene->node, c->pending.x, + c->pending.y); + c->animation.current = c->animainit_geom = c->animation.initial = + c->pending = c->current = c->geom; + client_apply_clip(c, 1.0); + c->need_output_flush = false; + } + } + + if (c->mon && is_canvas_layout(c->mon) && !c->isfullscreen && + !c->ismaximizescreen) { + uint32_t tag = c->mon->pertag->curtag; + float zoom = c->mon->pertag->canvas_zoom[tag]; + float effective_zoom = zoom; + if (c->mon->canvas_in_overview && + c->canvas_geom_backup[tag].width > 0) { + effective_zoom *= (float)c->canvas_geom[tag].width / + c->canvas_geom_backup[tag].width; + } + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, NULL); + client_apply_clip(c, 1.0); + if (effective_zoom != 1.0f && !c->is_clip_to_hide) + apply_canvas_zoom_correct(c, effective_zoom); + } + + if (!need_flush) { return client_apply_focus_opacity(c); } - if (config.animations && c->animation.running) { - client_animation_next_tick(c); - } else { - wlr_scene_node_set_position(&c->scene->node, c->pending.x, - c->pending.y); - c->animation.current = c->animainit_geom = c->animation.initial = - c->pending = c->current = c->geom; - client_apply_clip(c, 1.0); - c->need_output_flush = false; - } client_apply_focus_opacity(c); return true; } diff --git a/src/config/parse_config.h b/src/config/parse_config.h index e02b5017..b9412682 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -126,9 +126,36 @@ typedef struct { } // 默认按键绑定数组 -KeyBinding default_key_bindings[] = {CHVT(1), CHVT(2), CHVT(3), CHVT(4), - CHVT(5), CHVT(6), CHVT(7), CHVT(8), - CHVT(9), CHVT(10), CHVT(11), CHVT(12)}; +KeyBinding default_key_bindings[] = { + CHVT(1), + CHVT(2), + CHVT(3), + CHVT(4), + CHVT(5), + CHVT(6), + CHVT(7), + CHVT(8), + CHVT(9), + CHVT(10), + CHVT(11), + CHVT(12), + {WLR_MODIFIER_LOGO, + {.keysym = XKB_KEY_o, .type = KEY_TYPE_SYM}, + toggleminimap, + {0}}, + {WLR_MODIFIER_LOGO, + {.keysym = XKB_KEY_p, .type = KEY_TYPE_SYM}, + canvas_overview_toggle, + {0}}, + {WLR_MODIFIER_LOGO, + {.keysym = XKB_KEY_z, .type = KEY_TYPE_SYM}, + canvas_zoom_resize, + {.f = 1.0f / 1.1f}}, + {WLR_MODIFIER_LOGO, + {.keysym = XKB_KEY_x, .type = KEY_TYPE_SYM}, + canvas_zoom_resize, + {.f = 1.1f}}, +}; typedef struct { uint32_t mod; @@ -1052,6 +1079,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, func = togglefakefullscreen; } else if (strcmp(func_name, "toggleoverlay") == 0) { func = toggleoverlay; + } else if (strcmp(func_name, "toggleminimap") == 0) { + func = toggleminimap; } else if (strcmp(func_name, "minimized") == 0) { func = minimized; } else if (strcmp(func_name, "restore_minimized") == 0) { @@ -1204,6 +1233,13 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).i = parse_direction(arg_value); } else if (strcmp(func_name, "toggle_all_floating") == 0) { func = toggle_all_floating; + } else if (strcmp(func_name, "canvas_zoom_resize") == 0) { + func = canvas_zoom_resize; + (*arg).f = atof(arg_value); + } else if (strcmp(func_name, "canvas_overview_toggle") == 0) { + func = canvas_overview_toggle; + } else if (strcmp(func_name, "canvas_fill_viewport") == 0) { + func = canvas_fill_viewport; } else { return NULL; } diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index dbeebd33..e719919a 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -56,6 +56,7 @@ int32_t incohgaps(const Arg *arg); int32_t incovgaps(const Arg *arg); int32_t defaultgaps(const Arg *arg); int32_t togglefakefullscreen(const Arg *arg); +int32_t toggleminimap(const Arg *arg); int32_t toggleoverlay(const Arg *arg); int32_t movewin(const Arg *arg); int32_t resizewin(const Arg *arg); @@ -70,4 +71,7 @@ int32_t disable_monitor(const Arg *arg); int32_t enable_monitor(const Arg *arg); int32_t toggle_monitor(const Arg *arg); int32_t scroller_stack(const Arg *arg); +int32_t canvas_zoom_resize(const Arg *arg); +int32_t canvas_overview_toggle(const Arg *arg); +int32_t canvas_fill_viewport(const Arg *arg); int32_t toggle_all_floating(const Arg *arg); \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 07eafde5..5e2c359d 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -142,6 +142,8 @@ int32_t focusdir(const Arg *arg) { focusclient(c, 1); if (config.warpcursor) warp_cursor(c); + if (selmon && is_canvas_layout(selmon)) + canvas_pan_to_client(selmon, c); } else { if (config.focus_cross_tag) { if (arg->i == LEFT || arg->i == UP) @@ -374,7 +376,10 @@ int32_t moveresize(const Arg *arg) { return 0; } /* Float the window and tell motionnotify to grab it */ - if (grabc->isfloating == 0 && arg->ui == CurMove) { + if (grabc->isfloating == 0 && arg->ui == CurMove && + !(grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS)) { grabc->drag_to_tile = true; exit_scroller_stack(grabc); setfloating(grabc, 1); @@ -385,34 +390,52 @@ int32_t moveresize(const Arg *arg) { switch (cursor_mode = arg->ui) { case CurMove: - - grabcx = cursor->x - grabc->geom.x; - grabcy = cursor->y - grabc->geom.y; + if (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS) { + grabcx = (int32_t)round(cursor->x); + grabcy = (int32_t)round(cursor->y); + } else { + grabcx = cursor->x - grabc->geom.x; + grabcy = cursor->y - grabc->geom.y; + } wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab"); break; case CurResize: /* Doesn't work for X11 output - the next absolute motion event * returns the cursor to where it started */ - if (grabc->isfloating) { + if (grabc->isfloating || + (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS)) { rzcorner = config.drag_corner; grabcx = (int)round(cursor->x); grabcy = (int)round(cursor->y); + + int32_t vis_w = grabc->geom.width; + int32_t vis_h = grabc->geom.height; + if (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS) { + uint32_t _tag = grabc->mon->pertag->curtag; + float _zoom = grabc->mon->pertag->canvas_zoom[_tag]; + vis_w = (int32_t)roundf(vis_w * _zoom); + vis_h = (int32_t)roundf(vis_h * _zoom); + } + if (rzcorner == 4) /* identify the closest corner index */ - rzcorner = (grabcx - grabc->geom.x < - grabc->geom.x + grabc->geom.width - grabcx - ? 0 - : 1) + - (grabcy - grabc->geom.y < - grabc->geom.y + grabc->geom.height - grabcy - ? 0 - : 2); + rzcorner = + (grabcx - grabc->geom.x < grabc->geom.x + vis_w - grabcx + ? 0 + : 1) + + (grabcy - grabc->geom.y < grabc->geom.y + vis_h - grabcy + ? 0 + : 2); if (config.drag_warp_cursor) { - grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width - : grabc->geom.x; - grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height - : grabc->geom.y; + grabcx = rzcorner & 1 ? grabc->geom.x + vis_w : grabc->geom.x; + grabcy = rzcorner & 2 ? grabc->geom.y + vis_h : grabc->geom.y; wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); } @@ -1013,7 +1036,6 @@ int32_t switch_layout(const Arg *arg) { len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; - break; } } @@ -1667,6 +1689,10 @@ int32_t toggleoverview(const Arg *arg) { if (!selmon) return 0; + Client *fs_ov = focustop(selmon); + if (fs_ov && fs_ov->isfullscreen) + return 0; + if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && selmon->sel) { focusstack(&(Arg){.i = 1}); @@ -1871,6 +1897,86 @@ int32_t scroller_stack(const Arg *arg) { return 0; } +int32_t canvas_zoom_resize(const Arg *arg) { + if (!selmon || !is_canvas_layout(selmon)) + return 0; + + Client *fs = focustop(selmon); + if (fs && fs->isfullscreen) + return 0; + + float factor = arg->f; + if (factor <= 0.0f) + return 0; + + uint32_t tag = selmon->pertag->curtag; + float old_zoom = selmon->pertag->canvas_zoom[tag]; + selmon->pertag->canvas_zoom[tag] *= factor; + selmon->pertag->canvas_zoom[tag] = + CLAMP_FLOAT(selmon->pertag->canvas_zoom[tag], 0.1f, 1.0f); + float new_zoom = selmon->pertag->canvas_zoom[tag]; + + // Adjust pan so zoom centers on the screen center + float center_canvas_x = + selmon->pertag->canvas_pan_x[tag] + (selmon->w.width / old_zoom) / 2.0f; + float center_canvas_y = selmon->pertag->canvas_pan_y[tag] + + (selmon->w.height / old_zoom) / 2.0f; + selmon->pertag->canvas_pan_x[tag] = + center_canvas_x - (selmon->w.width / new_zoom) / 2.0f; + selmon->pertag->canvas_pan_y[tag] = + center_canvas_y - (selmon->w.height / new_zoom) / 2.0f; + + canvas_reposition(selmon); + return 0; +} + +int32_t canvas_overview_toggle(const Arg *arg) { + if (!selmon || !is_canvas_layout(selmon)) + return 0; + + if (selmon->canvas_overview_visible && !selmon->canvas_overview_closing) { + selmon->canvas_overview_closing = true; + selmon->canvas_overview_anim_start = get_now_in_ms(); + } else if (!selmon->canvas_overview_visible) { + if (selmon->minimap_visible) { + selmon->minimap_visible = false; + if (minimap_scene_tree) { + wlr_scene_node_destroy(&minimap_scene_tree->node); + minimap_scene_tree = NULL; + } + } + selmon->canvas_overview_visible = true; + selmon->canvas_overview_closing = false; + selmon->canvas_overview_progress = 0.0f; + selmon->canvas_overview_anim_start = get_now_in_ms(); + } + request_fresh_all_monitors(); + return 0; +} + +int32_t canvas_fill_viewport(const Arg *arg) { + if (!selmon || !is_canvas_layout(selmon)) + return 0; + + Client *c = selmon->sel; + if (!c || c->isfullscreen || c->ismaximizescreen) + return 0; + + uint32_t tag = selmon->pertag->curtag; + float zoom = selmon->pertag->canvas_zoom[tag]; + float pan_x = selmon->pertag->canvas_pan_x[tag]; + float pan_y = selmon->pertag->canvas_pan_y[tag]; + + c->canvas_geom[tag].x = (int32_t)roundf(pan_x); + c->canvas_geom[tag].y = (int32_t)roundf(pan_y); + c->canvas_geom[tag].width = (int32_t)roundf(selmon->w.width / zoom); + c->canvas_geom[tag].height = (int32_t)roundf(selmon->w.height / zoom); + c->iscustomsize = 1; + + arrange(selmon, false, false); + return 0; +} + int32_t toggle_all_floating(const Arg *arg) { if (!selmon || !selmon->sel) return 0; diff --git a/src/fetch/common.h b/src/fetch/common.h index 57a1a8e6..f7c8f918 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -96,10 +96,13 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, if (!node->enabled) continue; - if (node->type == WLR_SCENE_NODE_BUFFER) - surface = wlr_scene_surface_try_from_buffer( - wlr_scene_buffer_from_node(node)) - ->surface; + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (scene_surface) + surface = scene_surface->surface; + } /* start from the topmost layer, find a sureface that can be focused by pointer, diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index d2b4fe62..2cbf4536 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -26,6 +26,10 @@ bool is_scroller_layout(Monitor *m) { return false; } +bool is_canvas_layout(Monitor *m) { + return m->pertag->ltidxs[m->pertag->curtag]->id == CANVAS; +} + bool is_centertile_layout(Monitor *m) { if (m->pertag->ltidxs[m->pertag->curtag]->id == CENTER_TILE) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 04f4554b..3b8dbf31 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -813,6 +813,17 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, int32_t stack_index = 0; int32_t master_num = 0; int32_t stack_num = 0; + bool is_canvas = m->pertag->ltidxs[m->pertag->curtag]->id == CANVAS; + + if (!is_canvas) { + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && c->canvas_floating) { + c->isfloating = 0; + c->canvas_floating = false; + clear_visual_zoom(c); + } + } + } m->visible_clients = 0; m->visible_tiling_clients = 0; @@ -844,7 +855,7 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, if (!c->isunglobal) m->visible_clients++; - if (ISTILED(c)) { + if (ISTILED(c) && !is_canvas) { m->visible_tiling_clients++; } @@ -862,7 +873,7 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, if (c->mon == m) { if (VISIBLEON(c, m)) { - if (ISTILED(c)) { + if (ISTILED(c) && !is_canvas) { if (i < nmasters) { master_num++; @@ -916,6 +927,22 @@ arrange(Monitor *m, bool want_animation, bool from_view) { if (!m->wlr_output->enabled) return; + if (!is_canvas_layout(m) && m->canvas_in_overview) { + uint32_t tag = m->pertag->curtag; + Client *ov_c; + wl_list_for_each(ov_c, &clients, link) { + if (!VISIBLEON(ov_c, m) || ov_c->isunglobal) + continue; + if (ov_c->canvas_geom_backup[tag].width > 0) + ov_c->canvas_geom[tag] = ov_c->canvas_geom_backup[tag]; + memset(&ov_c->canvas_geom_backup[tag], 0, + sizeof(ov_c->canvas_geom_backup[tag])); + } + m->pertag->canvas_pan_x[tag] = m->canvas_saved_pan_x; + m->pertag->canvas_pan_y[tag] = m->canvas_saved_pan_y; + m->canvas_in_overview = false; + } + pre_caculate_before_arrange(m, want_animation, from_view, false); if (m->isoverview) { diff --git a/src/layout/canvas.h b/src/layout/canvas.h new file mode 100644 index 00000000..9ce9641d --- /dev/null +++ b/src/layout/canvas.h @@ -0,0 +1,146 @@ +static void canvas_geom_init(Client *c, Monitor *m, uint32_t tag, float pan_x, + float pan_y, int *cascade_idx) { + int w = c->geom.width > 0 ? c->geom.width : 640; + int h = c->geom.height > 0 ? c->geom.height : 480; + float zoom = m->pertag->canvas_zoom[tag]; + + float cx = pan_x + (m->w.width / 2.0f) / zoom; + float cy = pan_y + (m->w.height / 2.0f) / zoom; + + if (c->is_pending_open_animation) { + c->canvas_geom[tag].x = (int32_t)(cx - w / 2.0f); + c->canvas_geom[tag].y = (int32_t)(cy - h / 2.0f); + } else { + c->canvas_geom[tag].x = (int32_t)(cx + (*cascade_idx) * 30 - w / 2.0f); + c->canvas_geom[tag].y = (int32_t)(cy + (*cascade_idx) * 30 - h / 2.0f); + (*cascade_idx)++; + } + c->canvas_geom[tag].width = w; + c->canvas_geom[tag].height = h; +} + +static void canvas_reposition(Monitor *m) { + Client *c; + uint32_t tag = m->pertag->curtag; + + float pan_x = m->pertag->canvas_pan_x[tag]; + float pan_y = m->pertag->canvas_pan_y[tag]; + float zoom = m->pertag->canvas_zoom[tag]; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isunglobal) + continue; + if (c->isfullscreen || c->ismaximizescreen) + continue; + if (c->canvas_geom[tag].width <= 0 || c->canvas_geom[tag].height <= 0) + continue; + + int new_x = + m->w.x + (int32_t)roundf((c->canvas_geom[tag].x - pan_x) * zoom); + int new_y = + m->w.y + (int32_t)roundf((c->canvas_geom[tag].y - pan_y) * zoom); + + if (c->animation.running) { + c->animation.running = false; + c->need_output_flush = false; + } + + c->geom.x = new_x; + c->geom.y = new_y; + c->pending.x = new_x; + c->pending.y = new_y; + c->current.x = new_x; + c->current.y = new_y; + c->animation.current.x = new_x; + c->animation.current.y = new_y; + c->animation.initial.x = new_x; + c->animation.initial.y = new_y; + c->animainit_geom.x = new_x; + c->animainit_geom.y = new_y; + + wlr_scene_node_set_position(&c->scene->node, new_x, new_y); + + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, NULL); + client_apply_clip(c, 1.0); + if (zoom != 1.0f && !c->is_clip_to_hide) + apply_canvas_zoom_correct(c, zoom); + } +} + +static void canvas(Monitor *m) { + Client *c; + uint32_t tag = m->pertag->curtag; + + float pan_x = m->pertag->canvas_pan_x[tag]; + float pan_y = m->pertag->canvas_pan_y[tag]; + float zoom = m->pertag->canvas_zoom[tag]; + + int cascade_idx = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isunglobal) + continue; + + if (c->isfullscreen || c->ismaximizescreen) + continue; + + if (c->canvas_geom[tag].width == 0 && c->canvas_geom[tag].height == 0) + canvas_geom_init(c, m, tag, pan_x, pan_y, &cascade_idx); + + int screen_x = + m->w.x + (int32_t)roundf((c->canvas_geom[tag].x - pan_x) * zoom); + int screen_y = + m->w.y + (int32_t)roundf((c->canvas_geom[tag].y - pan_y) * zoom); + + float effective_zoom = zoom; + int32_t base_w = c->canvas_geom[tag].width; + int32_t base_h = c->canvas_geom[tag].height; + + if (m->canvas_in_overview && c->canvas_geom_backup[tag].width > 0) { + base_w = c->canvas_geom_backup[tag].width; + base_h = c->canvas_geom_backup[tag].height; + effective_zoom *= (float)c->canvas_geom[tag].width / base_w; + } + + struct wlr_box client_geom = { + .x = screen_x, + .y = screen_y, + .width = base_w, + .height = base_h, + }; + resize(c, client_geom, 0); + + if (effective_zoom == 1.0f) + clear_visual_zoom(c); + else + apply_visual_zoom(c, effective_zoom); + } +} + +static void canvas_pan_to_client(Monitor *m, Client *c) { + if (!m || !c || !is_canvas_layout(m)) + return; + + uint32_t tag = m->pertag->curtag; + if (c->canvas_geom[tag].width <= 0 || c->canvas_geom[tag].height <= 0) + return; + + float zoom = m->pertag->canvas_zoom[tag]; + float pan_x = m->pertag->canvas_pan_x[tag]; + float pan_y = m->pertag->canvas_pan_y[tag]; + float vp_w = m->w.width / zoom; + float vp_h = m->w.height / zoom; + + float cx = c->canvas_geom[tag].x; + float cy = c->canvas_geom[tag].y; + float cw = c->canvas_geom[tag].width; + float ch = c->canvas_geom[tag].height; + + if (cx >= pan_x && cy >= pan_y && cx + cw <= pan_x + vp_w && + cy + ch <= pan_y + vp_h) + return; + + m->pertag->canvas_pan_x[tag] = cx + cw / 2.0f - vp_w / 2.0f; + m->pertag->canvas_pan_y[tag] = cy + ch / 2.0f - vp_h / 2.0f; + canvas_reposition(m); +} diff --git a/src/layout/layout.h b/src/layout/layout.h index f896ac27..15722f3c 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -12,6 +12,7 @@ static void vertical_grid(Monitor *m); static void vertical_scroller(Monitor *m); static void vertical_deck(Monitor *mon); static void tgmix(Monitor *m); +static void canvas(Monitor *m); /* layout(s) */ Layout overviewlayout = {"󰃇", overview, "overview"}; @@ -29,6 +30,7 @@ enum { VERTICAL_DECK, RIGHT_TILE, TGMIX, + CANVAS, }; Layout layouts[] = { @@ -47,4 +49,5 @@ Layout layouts[] = { {"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局 {"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局 {"TG", tgmix, "tgmix", TGMIX}, // 混合布局 + {"CV", canvas, "canvas", CANVAS}, // canvas layout }; \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index fad86b20..3c028f6c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -93,6 +93,7 @@ #include #endif #include "common/util.h" +#include /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -117,6 +118,7 @@ ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) +#define CANVAS_MAX_TAGS 10 #define TAGMASK ((1 << LENGTH(tags)) - 1) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) #define ISFULLSCREEN(A) \ @@ -148,9 +150,9 @@ enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; enum { VERTICAL, HORIZONTAL }; enum { SWIPE_UP, SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT }; -enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ -enum { XDGShell, LayerShell, X11 }; /* client types */ -enum { AxisUp, AxisDown, AxisLeft, AxisRight }; // 滚轮滚动的方向 +enum { CurNormal, CurPressed, CurMove, CurResize, CurPan }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { AxisUp, AxisDown, AxisLeft, AxisRight }; // 滚轮滚动的方向 enum { LyrBg, LyrBlur, @@ -308,7 +310,8 @@ typedef struct { struct Client { /* Must keep these three elements in this order */ uint32_t type; /* XDGShell or X11* */ - struct wlr_box geom, pending, float_geom, animainit_geom, + struct wlr_box geom, pending, float_geom, canvas_geom[CANVAS_MAX_TAGS], + canvas_geom_backup[CANVAS_MAX_TAGS], animainit_geom, overview_backup_geom, current, drag_begin_geom; /* layout-relative, includes border */ Monitor *mon; @@ -396,6 +399,7 @@ struct Client { pid_t pid; Client *swallowing, *swallowedby; bool is_clip_to_hide; + bool canvas_floating; bool drag_to_tile; bool scratchpad_switching_mon; bool fake_no_border; @@ -504,6 +508,11 @@ typedef struct { struct Monitor { struct wl_list link; struct wlr_output *wlr_output; + bool minimap_visible; + bool canvas_overview_visible; + bool canvas_overview_closing; // true while animating out + float canvas_overview_progress; // 0.0 = hidden, 1.0 = fully visible + uint32_t canvas_overview_anim_start; struct wlr_scene_output *scene_output; struct wlr_output_state pending; struct wl_listener frame; @@ -539,6 +548,10 @@ struct Monitor { struct wlr_scene_optimized_blur *blur; char last_surface_ws_name[256]; struct wlr_ext_workspace_group_handle_v1 *ext_group; + bool canvas_in_overview; + float canvas_saved_pan_x; + float canvas_saved_pan_y; + float canvas_saved_zoom; }; typedef struct { @@ -769,7 +782,11 @@ static struct wlr_scene_tree * wlr_scene_tree_snapshot(struct wlr_scene_node *node, struct wlr_scene_tree *parent); static bool is_scroller_layout(Monitor *m); +static bool is_canvas_layout(Monitor *m); static bool is_centertile_layout(Monitor *m); +static bool cursor_in_minimap(Monitor *m, double cx, double cy); +static void get_canvas_bounds(Monitor *m, int *min_x, int *min_y, int *max_x, + int *max_y); static void create_output(struct wlr_backend *backend, void *data); static void get_layout_abbr(char *abbr, const char *full_name); static void apply_named_scratchpad(Client *target_client); @@ -819,7 +836,16 @@ static void *exclusive_focus; static struct wl_display *dpy; static struct wl_event_loop *event_loop; static struct wlr_backend *backend; -static struct wlr_backend *headless_backend; +static struct wlr_scene_tree *minimap_scene_tree = NULL; + +static struct wlr_scene_tree *overview_scene_tree = NULL; +static int overview_canvas_min_x = 0, overview_canvas_min_y = 0; +static float overview_scale = 1.0f; +static bool overview_built = false; +static struct wlr_scene_rect *overview_vp_rects[4] = { + NULL}; // top, bot, left, right +static int overview_tree_x = 0, overview_tree_y = 0; + static struct wlr_scene *scene; static struct wlr_scene_tree *layers[NUM_LAYERS]; static struct wlr_renderer *drw; @@ -886,6 +912,8 @@ static int32_t scroller_focus_lock = 0; static uint32_t swipe_fingers = 0; static double swipe_dx = 0; static double swipe_dy = 0; +static bool canvas_swipe_panning = false; +static double pinch_last_scale = 1.0; bool render_border = true; @@ -938,6 +966,9 @@ struct Pertag { int32_t open_as_floating[LENGTH(tags) + 1]; /* open_as_floating per tag */ const Layout *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ + float canvas_pan_x[LENGTH(tags) + 1]; + float canvas_pan_y[LENGTH(tags) + 1]; + float canvas_zoom[LENGTH(tags) + 1]; /* visual zoom factor, 1.0 = no zoom */ }; #include "config/parse_config.h" @@ -1004,10 +1035,13 @@ static struct wl_event_source *sync_keymap; #include "animation/common.h" #include "animation/layer.h" #include "animation/tag.h" +static void canvas_reposition(Monitor *m); +static void canvas_pan_to_client(Monitor *m, Client *c); #include "dispatch/bind_define.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" #include "layout/arrange.h" +#include "layout/canvas.h" #include "layout/horizontal.h" #include "layout/vertical.h" @@ -1087,6 +1121,24 @@ void show_scratchpad(Client *c) { resize(c, c->geom, 0); } + if (c->mon && is_canvas_layout(c->mon)) { + uint32_t tag = c->mon->pertag->curtag; + float pan_x = c->mon->pertag->canvas_pan_x[tag]; + float pan_y = c->mon->pertag->canvas_pan_y[tag]; + float zoom = c->mon->pertag->canvas_zoom[tag]; + int w = c->canvas_geom[tag].width > 0 ? c->canvas_geom[tag].width + : c->geom.width; + int h = c->canvas_geom[tag].height > 0 ? c->canvas_geom[tag].height + : c->geom.height; + + float cx = pan_x + (c->mon->w.width / 2.0f) / zoom; + float cy = pan_y + (c->mon->w.height / 2.0f) / zoom; + c->canvas_geom[tag].x = (int32_t)(cx - w / 2.0f); + c->canvas_geom[tag].y = (int32_t)(cy - h / 2.0f); + c->canvas_geom[tag].width = w; + c->canvas_geom[tag].height = h; + } + c->oldtags = c->mon->tagset[c->mon->seltags]; wl_list_remove(&c->link); // 从原来位置移除 wl_list_insert(clients.prev->next, &c->link); // 插入开头 @@ -1165,6 +1217,24 @@ bool switch_scratchpad_client_state(Client *c) { c->float_geom = setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0); + if (is_canvas_layout(c->mon)) { + uint32_t ctag = c->mon->pertag->curtag; + float cpan_x = c->mon->pertag->canvas_pan_x[ctag]; + float cpan_y = c->mon->pertag->canvas_pan_y[ctag]; + float czoom = c->mon->pertag->canvas_zoom[ctag]; + int cw = c->canvas_geom[ctag].width > 0 ? c->canvas_geom[ctag].width + : c->float_geom.width; + int ch = c->canvas_geom[ctag].height > 0 + ? c->canvas_geom[ctag].height + : c->float_geom.height; + float ccx = cpan_x + (c->mon->w.width / 2.0f) / czoom; + float ccy = cpan_y + (c->mon->w.height / 2.0f) / czoom; + c->canvas_geom[ctag].x = (int32_t)(ccx - cw / 2.0f); + c->canvas_geom[ctag].y = (int32_t)(ccy - ch / 2.0f); + c->canvas_geom[ctag].width = cw; + c->canvas_geom[ctag].height = ch; + } + // 只有显示状态的scratchpad才需要聚焦和返回true if (c->is_scratchpad_show) { c->tags = get_tags_first_tag(selmon->tagset[selmon->seltags]); @@ -1417,8 +1487,11 @@ void client_reset_mon_tags(Client *c, Monitor *mon, uint32_t newtags) { void check_match_tag_floating_rule(Client *c, Monitor *mon) { if (c->tags && !c->isfloating && mon && !c->swallowedby && - mon->pertag->open_as_floating[get_tags_first_tag_num(c->tags)]) { + (mon->pertag->open_as_floating[get_tags_first_tag_num(c->tags)] || + mon->pertag->ltidxs[mon->pertag->curtag]->id == CANVAS)) { c->isfloating = 1; + if (mon->pertag->ltidxs[mon->pertag->curtag]->id == CANVAS) + c->canvas_floating = true; } } @@ -1927,6 +2000,11 @@ int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { void swipe_begin(struct wl_listener *listener, void *data) { struct wlr_pointer_swipe_begin_event *event = data; + if (event->fingers == 3 && selmon && is_canvas_layout(selmon)) { + canvas_swipe_panning = true; + return; + } + // Forward swipe begin event to client wlr_pointer_gestures_v1_send_swipe_begin(pointer_gestures, seat, event->time_msec, event->fingers); @@ -1935,6 +2013,15 @@ void swipe_begin(struct wl_listener *listener, void *data) { void swipe_update(struct wl_listener *listener, void *data) { struct wlr_pointer_swipe_update_event *event = data; + if (canvas_swipe_panning && selmon && is_canvas_layout(selmon)) { + uint32_t tag = selmon->pertag->curtag; + float zoom = selmon->pertag->canvas_zoom[tag]; + selmon->pertag->canvas_pan_x[tag] -= event->dx / zoom; + selmon->pertag->canvas_pan_y[tag] -= event->dy / zoom; + canvas_reposition(selmon); + return; + } + swipe_fingers = event->fingers; // Accumulate swipe distance swipe_dx += event->dx; @@ -1947,6 +2034,12 @@ void swipe_update(struct wl_listener *listener, void *data) { void swipe_end(struct wl_listener *listener, void *data) { struct wlr_pointer_swipe_end_event *event = data; + + if (canvas_swipe_panning) { + canvas_swipe_panning = false; + return; + } + ongesture(event); swipe_dx = 0; swipe_dy = 0; @@ -1959,6 +2052,11 @@ void pinch_begin(struct wl_listener *listener, void *data) { struct wlr_pointer_pinch_begin_event *event = data; // Forward pinch begin event to client + pinch_last_scale = 1.0; + + if (selmon && is_canvas_layout(selmon)) + return; + wlr_pointer_gestures_v1_send_pinch_begin(pointer_gestures, seat, event->time_msec, event->fingers); } @@ -1966,7 +2064,28 @@ void pinch_begin(struct wl_listener *listener, void *data) { void pinch_update(struct wl_listener *listener, void *data) { struct wlr_pointer_pinch_update_event *event = data; - // Forward pinch update event to client + if (selmon && is_canvas_layout(selmon)) { + uint32_t tag = selmon->pertag->curtag; + float old_zoom = selmon->pertag->canvas_zoom[tag]; + + double ratio = + (pinch_last_scale > 0.0) ? event->scale / pinch_last_scale : 1.0; + pinch_last_scale = event->scale; + + float new_zoom = CLAMP_FLOAT(old_zoom * (float)ratio, 0.1f, 1.0f); + selmon->pertag->canvas_zoom[tag] = new_zoom; + + float cursor_sx = (float)(cursor->x - selmon->w.x); + float cursor_sy = (float)(cursor->y - selmon->w.y); + selmon->pertag->canvas_pan_x[tag] += + cursor_sx * (1.0f / old_zoom - 1.0f / new_zoom); + selmon->pertag->canvas_pan_y[tag] += + cursor_sy * (1.0f / old_zoom - 1.0f / new_zoom); + + canvas_reposition(selmon); + return; + } + wlr_pointer_gestures_v1_send_pinch_update( pointer_gestures, seat, event->time_msec, event->dx, event->dy, event->scale, event->rotation); @@ -1976,6 +2095,9 @@ void pinch_end(struct wl_listener *listener, void *data) { struct wlr_pointer_pinch_end_event *event = data; // Forward pinch end event to client + if (selmon && is_canvas_layout(selmon)) + return; + wlr_pointer_gestures_v1_send_pinch_end(pointer_gestures, seat, event->time_msec, event->cancelled); } @@ -2072,6 +2194,25 @@ buttonpress(struct wl_listener *listener, void *data) { if (locked) break; + { + struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); + uint32_t pmods = kb ? wlr_keyboard_get_modifiers(kb) : 0; + struct wlr_keyboard *hkb = &kb_group->wlr_group->keyboard; + uint32_t hmods = hkb ? wlr_keyboard_get_modifiers(hkb) : 0; + pmods = pmods | hmods; + if (event->button == BTN_MIDDLE && + (CLEANMASK(pmods) & WLR_MODIFIER_LOGO) && selmon && + selmon->pertag->ltidxs[selmon->pertag->curtag]->id == CANVAS && + !cursor_in_minimap(selmon, cursor->x, cursor->y) && + !(focustop(selmon) && focustop(selmon)->isfullscreen)) { + grabcx = (int32_t)round(cursor->x); + grabcy = (int32_t)round(cursor->y); + cursor_mode = CurPan; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "grabbing"); + return; + } + } + xytonode(cursor->x, cursor->y, &surface, NULL, NULL, NULL, NULL); if (toplevel_from_wlr_surface(surface, &c, &l) >= 0) { if (c && c->scene->node.enabled && @@ -2112,6 +2253,54 @@ buttonpress(struct wl_listener *listener, void *data) { return; } + if (selmon->canvas_overview_visible && + !selmon->canvas_overview_closing && event->button == BTN_LEFT && + selmon->pertag->ltidxs[selmon->pertag->curtag]->id == CANVAS) { + uint32_t tag = selmon->pertag->curtag; + float zoom = selmon->pertag->canvas_zoom[tag]; + + int min_x, min_y, max_x, max_y; + get_canvas_bounds(selmon, &min_x, &min_y, &max_x, &max_y); + int canvas_w = max_x - min_x; + int canvas_h = max_y - min_y; + if (canvas_w < 100) + canvas_w = 100; + if (canvas_h < 100) + canvas_h = 100; + + float screen_aspect = (float)selmon->m.width / selmon->m.height; + float canvas_aspect = (float)canvas_w / canvas_h; + if (canvas_aspect < screen_aspect) { + int new_w = (int)(canvas_h * screen_aspect); + int pad = (new_w - canvas_w) / 2; + min_x -= pad; + canvas_w = new_w; + } else if (canvas_aspect > screen_aspect) { + int new_h = (int)(canvas_w / screen_aspect); + int pad = (new_h - canvas_h) / 2; + min_y -= pad; + canvas_h = new_h; + } + + float scale = (float)selmon->m.width / canvas_w; + + float click_x = cursor->x - selmon->m.x; + float click_y = cursor->y - selmon->m.y; + float canvas_x = click_x / scale + min_x; + float canvas_y = click_y / scale + min_y; + + selmon->pertag->canvas_pan_x[tag] = + canvas_x - (selmon->w.width / zoom) / 2.0f; + selmon->pertag->canvas_pan_y[tag] = + canvas_y - (selmon->w.height / zoom) / 2.0f; + + selmon->canvas_overview_closing = true; + selmon->canvas_overview_anim_start = get_now_in_ms(); + arrange(selmon, true, false); + request_fresh_all_monitors(); + return; + } + if (selmon->isoverview && event->button == BTN_RIGHT && c) { pending_kill_client(c); return; @@ -2127,6 +2316,12 @@ buttonpress(struct wl_listener *listener, void *data) { } break; case WL_POINTER_BUTTON_STATE_RELEASED: + if (!locked && cursor_mode == CurPan) { + cursor_mode = CurNormal; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + motionnotify(0, NULL, 0, 0, 0, 0); + return; + } /* If you released any buttons, we exit interactive move/resize mode. */ if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { cursor_mode = CurNormal; @@ -2140,8 +2335,14 @@ buttonpress(struct wl_listener *listener, void *data) { selmon->sel = NULL; } selmon = xytomon(cursor->x, cursor->y); - client_update_oldmonname_record(grabc, selmon); - setmon(grabc, selmon, 0, true); + if (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS) { + selmon = grabc->mon; + } else { + client_update_oldmonname_record(grabc, selmon); + setmon(grabc, selmon, 0, true); + } selmon->prevsel = ISTILED(selmon->sel) ? selmon->sel : NULL; selmon->sel = grabc; tmpc = grabc; @@ -2610,6 +2811,20 @@ void commitnotify(struct wl_listener *listener, void *data) { new_geo->x != 0 || new_geo->y != 0; } + if (c->mon && is_canvas_layout(c->mon) && !c->isfullscreen && + !c->ismaximizescreen) { + uint32_t tag = c->mon->pertag->curtag; + float zoom = c->mon->pertag->canvas_zoom[tag]; + float effective_zoom = zoom; + if (c->mon->canvas_in_overview && + c->canvas_geom_backup[tag].width > 0) { + effective_zoom *= (float)c->canvas_geom[tag].width / + c->canvas_geom_backup[tag].width; + } + if (effective_zoom != 1.0f) + apply_visual_zoom(c, effective_zoom); + } + if (c == grabc || !c->dirty) return; @@ -2973,6 +3188,22 @@ bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, } void createmon(struct wl_listener *listener, void *data) { + if (wlr_output_is_headless((struct wlr_output *)data)) { + if (!wlr_output_init_render((struct wlr_output *)data, alloc, drw)) + return; + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = + wlr_output_preferred_mode((struct wlr_output *)data); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state((struct wlr_output *)data, &state); + wlr_output_state_finish(&state); + return; + } + /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ struct wlr_output *wlr_output = data; @@ -3079,6 +3310,7 @@ void createmon(struct wl_listener *listener, void *data) { m->pertag->nmasters[i] = config.default_nmaster; m->pertag->mfacts[i] = config.default_mfact; m->pertag->ltidxs[i] = &layouts[0]; + m->pertag->canvas_zoom[i] = 1.0f; } // apply tag rule @@ -4114,6 +4346,10 @@ mapnotify(struct wl_listener *listener, void *data) { if (client_is_x11(c)) init_client_properties(c); + if (c->mon && c->mon->canvas_in_overview) { + canvas_overview_toggle(&(Arg){0}); + } + // set special window properties if (client_is_unmanaged(c) || client_is_x11_popup(c)) { c->bw = 0; @@ -4317,22 +4553,77 @@ void resize_floating_window(Client *grabc) { int cdx = (int)round(cursor->x) - grabcx; int cdy = (int)round(cursor->y) - grabcy; - cdx = !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1 - ? 0 - : cdx; - cdy = !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1 - ? 0 - : cdy; + if (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == CANVAS) { + uint32_t tag = grabc->mon->pertag->curtag; + float zoom = grabc->mon->pertag->canvas_zoom[tag]; + float pan_x = grabc->mon->pertag->canvas_pan_x[tag]; + float pan_y = grabc->mon->pertag->canvas_pan_y[tag]; - const struct wlr_box box = { - .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx), - .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy), - .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx), - .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy)}; + float canvas_cdx = (float)cdx / zoom; + float canvas_cdy = (float)cdy / zoom; - grabc->float_geom = box; + if (!(rzcorner & 1) && grabc->canvas_geom[tag].width - + 2 * (int)grabc->bw - (int)canvas_cdx < + 1) { + canvas_cdx = 0; + cdx = 0; + } + if (!(rzcorner & 2) && grabc->canvas_geom[tag].height - + 2 * (int)grabc->bw - (int)canvas_cdy < + 1) { + canvas_cdy = 0; + cdy = 0; + } - resize(grabc, box, 1); + if (!(rzcorner & 1)) { + grabc->canvas_geom[tag].x = + (int32_t)roundf(grabc->canvas_geom[tag].x + canvas_cdx); + grabc->canvas_geom[tag].width = + (int32_t)roundf(grabc->canvas_geom[tag].width - canvas_cdx); + } else { + grabc->canvas_geom[tag].width = + (int32_t)roundf(grabc->canvas_geom[tag].width + canvas_cdx); + } + if (!(rzcorner & 2)) { + grabc->canvas_geom[tag].y = + (int32_t)roundf(grabc->canvas_geom[tag].y + canvas_cdy); + grabc->canvas_geom[tag].height = + (int32_t)roundf(grabc->canvas_geom[tag].height - canvas_cdy); + } else { + grabc->canvas_geom[tag].height = + (int32_t)roundf(grabc->canvas_geom[tag].height + canvas_cdy); + } + + const struct wlr_box box = { + .x = grabc->mon->w.x + + (int32_t)roundf((grabc->canvas_geom[tag].x - pan_x) * zoom), + .y = grabc->mon->w.y + + (int32_t)roundf((grabc->canvas_geom[tag].y - pan_y) * zoom), + .width = grabc->canvas_geom[tag].width, + .height = grabc->canvas_geom[tag].height, + }; + grabc->float_geom = box; + resize(grabc, box, 1); + } else { + cdx = + !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1 + ? 0 + : cdx; + cdy = + !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1 + ? 0 + : cdy; + + const struct wlr_box box = { + .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx), + .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy), + .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx), + .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy)}; + + grabc->float_geom = box; + resize(grabc, box, 1); + } grabcx += cdx; grabcy += cdy; } @@ -4383,6 +4674,10 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, selmon = xytomon(cursor->x, cursor->y); } + if (selmon && selmon->canvas_overview_visible && + !selmon->canvas_overview_closing) + request_fresh_all_monitors(); + /* Find the client under the pointer and send the event along. */ xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); @@ -4402,7 +4697,24 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, /* If we are currently grabbing the mouse, handle and return */ if (cursor_mode == CurMove) { - /* Move the grabbed client to the new position. */ + if (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS) { + if (!time) + return; + int dx = (int32_t)round(cursor->x) - grabcx; + int dy = (int32_t)round(cursor->y) - grabcy; + grabcx = (int32_t)round(cursor->x); + grabcy = (int32_t)round(cursor->y); + + uint32_t tag = grabc->mon->pertag->curtag; + float zoom = grabc->mon->pertag->canvas_zoom[tag]; + grabc->canvas_geom[tag].x += (int32_t)roundf(dx / zoom); + grabc->canvas_geom[tag].y += (int32_t)roundf(dy / zoom); + + arrange(grabc->mon, false, false); + return; + } grabc->iscustomsize = 1; grabc->float_geom = (struct wlr_box){.x = (int32_t)round(cursor->x) - grabcx, @@ -4412,7 +4724,10 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, resize(grabc, grabc->float_geom, 1); return; } else if (cursor_mode == CurResize) { - if (grabc->isfloating) { + if (grabc->isfloating || + (grabc->mon && + grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]->id == + CANVAS)) { grabc->iscustomsize = 1; if (last_apply_drap_time == 0 || time - last_apply_drap_time > @@ -4424,6 +4739,21 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, } else { resize_tile_client(grabc, true, 0, 0, time); } + } else if (cursor_mode == CurPan) { + if (!time) + return; + int dx = (int32_t)round(cursor->x) - grabcx; + int dy = (int32_t)round(cursor->y) - grabcy; + grabcx = (int32_t)round(cursor->x); + grabcy = (int32_t)round(cursor->y); + + uint32_t tag = selmon->pertag->curtag; + float zoom = selmon->pertag->canvas_zoom[tag]; + selmon->pertag->canvas_pan_x[tag] -= dx / zoom; + selmon->pertag->canvas_pan_y[tag] -= dy / zoom; + + canvas_reposition(selmon); + return; } /* If there's no client surface under the cursor, set the cursor image @@ -4636,6 +4966,123 @@ void monitor_check_skip_frame_timeout(Monitor *m) { } } +static void get_canvas_bounds(Monitor *m, int *min_x, int *min_y, int *max_x, + int *max_y) { + Client *c; + uint32_t tag = m->pertag->curtag; + bool found = false; + + *min_x = INT32_MAX; + *min_y = INT32_MAX; + *max_x = INT32_MIN; + *max_y = INT32_MIN; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isminimized) + continue; + if (c->canvas_geom[tag].width <= 0 || c->canvas_geom[tag].height <= 0) + continue; + + int cx = c->canvas_geom[tag].x; + int cy = c->canvas_geom[tag].y; + int cw = c->canvas_geom[tag].width; + int ch = c->canvas_geom[tag].height; + + if (cx < *min_x) + *min_x = cx; + if (cy < *min_y) + *min_y = cy; + if (cx + cw > *max_x) + *max_x = cx + cw; + if (cy + ch > *max_y) + *max_y = cy + ch; + found = true; + } + + if (!found) { + *min_x = -m->m.width; + *min_y = -m->m.height; + *max_x = m->m.width; + *max_y = m->m.height; + } + + int margin = 100; + *min_x -= margin; + *min_y -= margin; + *max_x += margin; + *max_y += margin; +} + +static bool cursor_in_minimap(Monitor *m, double cx, double cy) { + if (!m || !m->minimap_visible) + return false; + + int minimap_w = m->m.width / 4; + int minimap_h = m->m.height / 4; + int margin = 20; + + int mx = m->m.x + m->m.width - minimap_w - margin; + int my = m->m.y + margin; + + return cx >= mx && cx < mx + minimap_w && cy >= my && cy < my + minimap_h; +} + +int32_t toggleminimap(const Arg *arg) { + if (!selmon) + return 0; + + Client *fs_mm = focustop(selmon); + if (fs_mm && fs_mm->isfullscreen) + return 0; + + selmon->minimap_visible = !selmon->minimap_visible; + if (selmon->minimap_visible) { + if (selmon->canvas_overview_visible && + !selmon->canvas_overview_closing) { + selmon->canvas_overview_closing = true; + selmon->canvas_overview_anim_start = get_now_in_ms(); + } + } else { + if (minimap_scene_tree) { + wlr_scene_node_destroy(&minimap_scene_tree->node); + minimap_scene_tree = NULL; + } + if (is_canvas_layout(selmon)) + canvas_reposition(selmon); + request_fresh_all_monitors(); + } + return 0; +} + +struct thumbnail_ctx { + struct wlr_scene_tree *tree; + int rx, ry; + float surf_scale; + bool any_rendered; +}; + +static void render_surface_thumbnail(struct wlr_surface *surface, int sx, + int sy, void *data) { + struct thumbnail_ctx *ctx = data; + if (!surface->buffer) + return; + int sw = (int)(surface->current.width * ctx->surf_scale); + int sh = (int)(surface->current.height * ctx->surf_scale); + if (sw < 1) + sw = 1; + if (sh < 1) + sh = 1; + struct wlr_scene_buffer *sbuf = + wlr_scene_buffer_create(ctx->tree, &surface->buffer->base); + if (!sbuf) + return; + wlr_scene_buffer_set_dest_size(sbuf, sw, sh); + wlr_scene_node_set_position(&sbuf->node, + ctx->rx + (int)(sx * ctx->surf_scale), + ctx->ry + (int)(sy * ctx->surf_scale)); + ctx->any_rendered = true; +} + void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; @@ -4686,6 +5133,443 @@ void rendermon(struct wl_listener *listener, void *data) { } // 只有在需要帧时才构建和提交状态 + + if (m->minimap_visible && + m->pertag->ltidxs[m->pertag->curtag]->id == CANVAS) { + uint32_t tag = m->pertag->curtag; + + int min_x, min_y, max_x, max_y; + get_canvas_bounds(m, &min_x, &min_y, &max_x, &max_y); + + int canvas_w = max_x - min_x; + int canvas_h = max_y - min_y; + if (canvas_w < 100) + canvas_w = 100; + if (canvas_h < 100) + canvas_h = 100; + + int minimap_w = m->m.width / 4; + int minimap_h = m->m.height / 4; + int margin = 20; + + float scale_x = (float)minimap_w / canvas_w; + float scale_y = (float)minimap_h / canvas_h; + float scale = (scale_x < scale_y) ? scale_x : scale_y; + + if (minimap_scene_tree) { + wlr_scene_node_destroy(&minimap_scene_tree->node); + } + minimap_scene_tree = wlr_scene_tree_create(layers[LyrBlock]); + + wlr_scene_node_set_position(&minimap_scene_tree->node, + m->m.x + m->m.width - minimap_w - margin, + m->m.y + margin); + + bool wallpaper_set = false; + LayerSurface *l; + wl_list_for_each(l, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + link) { + if (l->layer_surface && l->layer_surface->surface && + l->layer_surface->surface->buffer && l->mapped) { + struct wlr_buffer *wbuf = + &l->layer_surface->surface->buffer->base; + struct wlr_scene_buffer *bg_buf = + wlr_scene_buffer_create(minimap_scene_tree, wbuf); + if (bg_buf) { + wlr_scene_buffer_set_dest_size(bg_buf, minimap_w, + minimap_h); + wlr_scene_node_set_position(&bg_buf->node, 0, 0); + wallpaper_set = true; + break; + } + } + } + if (!wallpaper_set) { + struct wlr_scene_rect *bg = + wlr_scene_rect_create(minimap_scene_tree, minimap_w, minimap_h, + (float[4]){0.1f, 0.1f, 0.15f, 0.9f}); + (void)bg; + } + + Client *c; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isminimized) + continue; + if (c->canvas_geom[tag].width <= 0 || + c->canvas_geom[tag].height <= 0) + continue; + + struct wlr_surface *surface = client_surface(c); + if (!surface || !surface->mapped) + continue; + + int rx = (int)((c->canvas_geom[tag].x - min_x) * scale); + int ry = (int)((c->canvas_geom[tag].y - min_y) * scale); + int rw = (int)(c->canvas_geom[tag].width * scale); + int rh = (int)(c->canvas_geom[tag].height * scale); + + if (rw < 4) + rw = 4; + if (rh < 4) + rh = 4; + + struct thumbnail_ctx tctx = { + .tree = minimap_scene_tree, + .rx = rx, + .ry = ry, + .surf_scale = c->canvas_geom[tag].width > 0 + ? (float)rw / c->canvas_geom[tag].width + : scale, + .any_rendered = false, + }; + wlr_surface_for_each_surface(surface, render_surface_thumbnail, + &tctx); + if (!tctx.any_rendered) { + float color[4] = {0.3f, 0.5f, 0.7f, 0.8f}; + if (c == focustop(m)) { + color[0] = 0.8f; + color[1] = 0.6f; + color[2] = 0.2f; + } + struct wlr_scene_rect *wr = + wlr_scene_rect_create(minimap_scene_tree, rw, rh, color); + wlr_scene_node_set_position(&wr->node, rx, ry); + } + } + + float pan_x = m->pertag->canvas_pan_x[tag]; + float pan_y = m->pertag->canvas_pan_y[tag]; + float zoom = m->pertag->canvas_zoom[tag]; + + int vp_x = (int)((pan_x - min_x) * scale); + int vp_y = (int)((pan_y - min_y) * scale); + int vp_w = (int)((m->w.width / zoom) * scale); + int vp_h = (int)((m->w.height / zoom) * scale); + + if (vp_w < 4) + vp_w = 4; + if (vp_h < 4) + vp_h = 4; + + float vp_color[4] = {1.0f, 1.0f, 1.0f, 0.6f}; + int border = 2; + struct wlr_scene_rect *vp_top = + wlr_scene_rect_create(minimap_scene_tree, vp_w, border, vp_color); + wlr_scene_node_set_position(&vp_top->node, vp_x, vp_y); + struct wlr_scene_rect *vp_bot = + wlr_scene_rect_create(minimap_scene_tree, vp_w, border, vp_color); + wlr_scene_node_set_position(&vp_bot->node, vp_x, vp_y + vp_h - border); + struct wlr_scene_rect *vp_left = + wlr_scene_rect_create(minimap_scene_tree, border, vp_h, vp_color); + wlr_scene_node_set_position(&vp_left->node, vp_x, vp_y); + struct wlr_scene_rect *vp_right = + wlr_scene_rect_create(minimap_scene_tree, border, vp_h, vp_color); + wlr_scene_node_set_position(&vp_right->node, vp_x + vp_w - border, + vp_y); + + } else if (!m->minimap_visible && minimap_scene_tree) { + wlr_scene_node_destroy(&minimap_scene_tree->node); + minimap_scene_tree = NULL; + } + + if (m->canvas_overview_visible && + m->pertag->ltidxs[m->pertag->curtag]->id == CANVAS) { + uint32_t tag = m->pertag->curtag; + + uint32_t anim_duration = 300; + uint32_t elapsed = get_now_in_ms() - m->canvas_overview_anim_start; + double raw_t = + anim_duration ? (double)elapsed / (double)anim_duration : 1.0; + if (raw_t > 1.0) + raw_t = 1.0; + + bool animating = false; + if (m->canvas_overview_closing) { + double factor = find_animation_curve_at(raw_t, OPAFADEOUT); + m->canvas_overview_progress = 1.0f - (float)factor; + if (raw_t >= 1.0) { + m->canvas_overview_visible = false; + m->canvas_overview_closing = false; + m->canvas_overview_progress = 0.0f; + overview_built = false; + if (overview_scene_tree) { + wlr_scene_node_destroy(&overview_scene_tree->node); + overview_scene_tree = NULL; + } + memset(overview_vp_rects, 0, sizeof(overview_vp_rects)); + goto overview_done; + } + animating = true; + need_more_frames = true; + } else { + double factor = find_animation_curve_at(raw_t, OPAFADEIN); + m->canvas_overview_progress = (float)factor; + if (raw_t < 1.0) { + animating = true; + need_more_frames = true; + } + } + + float prog = m->canvas_overview_progress; + + if (!animating && overview_built && overview_scene_tree) { + float zoom = m->pertag->canvas_zoom[tag]; + float pan_x = m->pertag->canvas_pan_x[tag]; + float pan_y = m->pertag->canvas_pan_y[tag]; + + float cx = cursor->x - overview_tree_x; + float cy = cursor->y - overview_tree_y; + if (cx >= 0 && cx < m->m.width && cy >= 0 && cy < m->m.height) { + float canvas_cx = cx / overview_scale + overview_canvas_min_x; + float canvas_cy = cy / overview_scale + overview_canvas_min_y; + pan_x = canvas_cx - (m->w.width / zoom) / 2.0f; + pan_y = canvas_cy - (m->w.height / zoom) / 2.0f; + } + + int vp_x = (int)((pan_x - overview_canvas_min_x) * overview_scale); + int vp_y = (int)((pan_y - overview_canvas_min_y) * overview_scale); + int vp_w = (int)((m->w.width / zoom) * overview_scale); + int vp_h = (int)((m->w.height / zoom) * overview_scale); + if (vp_w < 8) + vp_w = 8; + if (vp_h < 8) + vp_h = 8; + + int border = 3; + if (overview_vp_rects[0]) { + wlr_scene_rect_set_size(overview_vp_rects[0], vp_w, border); + wlr_scene_node_set_position(&overview_vp_rects[0]->node, vp_x, + vp_y); + } + if (overview_vp_rects[1]) { + wlr_scene_rect_set_size(overview_vp_rects[1], vp_w, border); + wlr_scene_node_set_position(&overview_vp_rects[1]->node, vp_x, + vp_y + vp_h - border); + } + if (overview_vp_rects[2]) { + wlr_scene_rect_set_size(overview_vp_rects[2], border, vp_h); + wlr_scene_node_set_position(&overview_vp_rects[2]->node, vp_x, + vp_y); + } + if (overview_vp_rects[3]) { + wlr_scene_rect_set_size(overview_vp_rects[3], border, vp_h); + wlr_scene_node_set_position(&overview_vp_rects[3]->node, + vp_x + vp_w - border, vp_y); + } + goto overview_done; + } + + { + Client *ci; + int cascade_idx = 0; + uint32_t ctag = m->pertag->curtag; + float cpx = m->pertag->canvas_pan_x[ctag]; + float cpy = m->pertag->canvas_pan_y[ctag]; + wl_list_for_each(ci, &clients, link) { + if (!VISIBLEON(ci, m) || ci->isminimized) + continue; + if (ci->canvas_geom[ctag].width <= 0 || + ci->canvas_geom[ctag].height <= 0) + canvas_geom_init(ci, m, ctag, cpx, cpy, &cascade_idx); + } + } + + int min_x, min_y, max_x, max_y; + get_canvas_bounds(m, &min_x, &min_y, &max_x, &max_y); + + int canvas_w = max_x - min_x; + int canvas_h = max_y - min_y; + if (canvas_w < 100) + canvas_w = 100; + if (canvas_h < 100) + canvas_h = 100; + + float screen_aspect = (float)m->m.width / m->m.height; + float canvas_aspect = (float)canvas_w / canvas_h; + if (canvas_aspect < screen_aspect) { + int new_w = (int)(canvas_h * screen_aspect); + int pad = (new_w - canvas_w) / 2; + min_x -= pad; + max_x += pad; + canvas_w = new_w; + } else if (canvas_aspect > screen_aspect) { + int new_h = (int)(canvas_w / screen_aspect); + int pad = (new_h - canvas_h) / 2; + min_y -= pad; + max_y += pad; + canvas_h = new_h; + } + + float scale = (float)m->m.width / canvas_w; + + if (overview_scene_tree) { + wlr_scene_node_destroy(&overview_scene_tree->node); + overview_scene_tree = NULL; + } + memset(overview_vp_rects, 0, sizeof(overview_vp_rects)); + overview_scene_tree = wlr_scene_tree_create(layers[LyrBlock]); + if (!overview_scene_tree) + goto overview_done; + + int final_x = m->m.x; + int final_y = m->m.y; + int center_x = m->m.x + m->m.width / 2; + int center_y = m->m.y + m->m.height / 2; + int tree_x = final_x + (int)((center_x - final_x) * (1.0f - prog)); + int tree_y = final_y + (int)((center_y - final_y) * (1.0f - prog)); + + wlr_scene_node_set_position(&overview_scene_tree->node, tree_x, tree_y); + + struct wlr_scene_rect *fullbg = + wlr_scene_rect_create(overview_scene_tree, m->m.width, m->m.height, + (float[4]){0.0f, 0.0f, 0.0f, prog}); + if (fullbg) + wlr_scene_node_set_position(&fullbg->node, m->m.x - tree_x, + m->m.y - tree_y); + + int anim_content_w = (int)(m->m.width * prog); + int anim_content_h = (int)(m->m.height * prog); + if (anim_content_w < 1) + anim_content_w = 1; + if (anim_content_h < 1) + anim_content_h = 1; + + bool wallpaper_set = false; + LayerSurface *l; + wl_list_for_each(l, &m->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + link) { + if (l->layer_surface && l->layer_surface->surface && + l->layer_surface->surface->buffer && l->mapped) { + struct wlr_buffer *wbuf = + &l->layer_surface->surface->buffer->base; + struct wlr_scene_buffer *bg_buf = + wlr_scene_buffer_create(overview_scene_tree, wbuf); + if (bg_buf) { + wlr_scene_buffer_set_dest_size(bg_buf, anim_content_w, + anim_content_h); + wlr_scene_node_set_position(&bg_buf->node, 0, 0); + wallpaper_set = true; + break; + } + } + } + if (!wallpaper_set) { + struct wlr_scene_rect *bg = wlr_scene_rect_create( + overview_scene_tree, anim_content_w, anim_content_h, + (float[4]){0.15f, 0.15f, 0.2f, prog}); + (void)bg; + } + + Client *c; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isminimized) + continue; + if (c->canvas_geom[tag].width <= 0 || + c->canvas_geom[tag].height <= 0) + continue; + + struct wlr_surface *surface = client_surface(c); + if (!surface || !surface->mapped) + continue; + + int rx = (int)((c->canvas_geom[tag].x - min_x) * scale * prog); + int ry = (int)((c->canvas_geom[tag].y - min_y) * scale * prog); + int rw = (int)(c->canvas_geom[tag].width * scale * prog); + int rh = (int)(c->canvas_geom[tag].height * scale * prog); + + if (rw < 1) + rw = 1; + if (rh < 1) + rh = 1; + + struct thumbnail_ctx tctx = { + .tree = overview_scene_tree, + .rx = rx, + .ry = ry, + .surf_scale = c->canvas_geom[tag].width > 0 + ? (float)rw / c->canvas_geom[tag].width + : scale * prog, + .any_rendered = false, + }; + wlr_surface_for_each_surface(surface, render_surface_thumbnail, + &tctx); + if (!tctx.any_rendered) { + float color[4] = {0.3f, 0.5f, 0.7f, prog}; + if (c == focustop(m)) { + color[0] = 0.9f; + color[1] = 0.7f; + color[2] = 0.3f; + } + struct wlr_scene_rect *wr = + wlr_scene_rect_create(overview_scene_tree, rw, rh, color); + if (wr) + wlr_scene_node_set_position(&wr->node, rx, ry); + } + } + + float pan_x = m->pertag->canvas_pan_x[tag]; + float pan_y = m->pertag->canvas_pan_y[tag]; + float zoom = m->pertag->canvas_zoom[tag]; + + if (prog >= 1.0f) { + float cx = cursor->x - final_x; + float cy = cursor->y - final_y; + if (cx >= 0 && cx < m->m.width && cy >= 0 && cy < m->m.height) { + float canvas_cx = cx / scale + min_x; + float canvas_cy = cy / scale + min_y; + pan_x = canvas_cx - (m->w.width / zoom) / 2.0f; + pan_y = canvas_cy - (m->w.height / zoom) / 2.0f; + } + } + + int vp_x = (int)((pan_x - min_x) * scale * prog); + int vp_y = (int)((pan_y - min_y) * scale * prog); + int vp_w = (int)((m->w.width / zoom) * scale * prog); + int vp_h = (int)((m->w.height / zoom) * scale * prog); + if (vp_w < 8) + vp_w = 8; + if (vp_h < 8) + vp_h = 8; + + float vp_color[4] = {1.0f, 1.0f, 1.0f, 0.8f * prog}; + int border = 3; + overview_vp_rects[0] = + wlr_scene_rect_create(overview_scene_tree, vp_w, border, vp_color); + if (overview_vp_rects[0]) + wlr_scene_node_set_position(&overview_vp_rects[0]->node, vp_x, + vp_y); + overview_vp_rects[1] = + wlr_scene_rect_create(overview_scene_tree, vp_w, border, vp_color); + if (overview_vp_rects[1]) + wlr_scene_node_set_position(&overview_vp_rects[1]->node, vp_x, + vp_y + vp_h - border); + overview_vp_rects[2] = + wlr_scene_rect_create(overview_scene_tree, border, vp_h, vp_color); + if (overview_vp_rects[2]) + wlr_scene_node_set_position(&overview_vp_rects[2]->node, vp_x, + vp_y); + overview_vp_rects[3] = + wlr_scene_rect_create(overview_scene_tree, border, vp_h, vp_color); + if (overview_vp_rects[3]) + wlr_scene_node_set_position(&overview_vp_rects[3]->node, + vp_x + vp_w - border, vp_y); + + overview_canvas_min_x = min_x; + overview_canvas_min_y = min_y; + overview_scale = scale; + overview_tree_x = tree_x; + overview_tree_y = tree_y; + + if (!animating) + overview_built = true; + overview_done:; + } else if (!m->canvas_overview_visible && overview_scene_tree) { + wlr_scene_node_destroy(&overview_scene_tree->node); + overview_scene_tree = NULL; + overview_built = false; + memset(overview_vp_rects, 0, sizeof(overview_vp_rects)); + } + if (config.allow_tearing && frame_allow_tearing) { apply_tear_state(m); } else { @@ -5257,6 +6141,22 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, c->mon->m, 1); c->isfullscreen = 1; + + if (is_canvas_layout(c->mon)) { + c->animation.running = false; + c->need_output_flush = false; + c->animation.current = c->animainit_geom = c->animation.initial = + c->pending = c->current = c->geom; + wlr_scene_node_set_position(&c->scene->node, c->mon->m.x, + c->mon->m.y); + wlr_scene_node_set_position(&c->scene_surface->node, 0, 0); + clear_visual_zoom(c); + struct wlr_box full_clip = {0, 0, c->mon->m.width, + c->mon->m.height}; + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, + &full_clip); + request_fresh_all_monitors(); + } } else { c->bw = c->isnoborder ? 0 : config.borderpx; c->isfullscreen = 0; @@ -5527,13 +6427,6 @@ void setup(void) { if (!(backend = wlr_backend_autocreate(event_loop, &session))) die("couldn't create backend"); - headless_backend = wlr_headless_backend_create(event_loop); - if (!headless_backend) { - wlr_log(WLR_ERROR, "Failed to create secondary headless backend"); - } else { - wlr_multi_backend_add(backend, headless_backend); - } - /* Initialize the scene graph used to lay out windows */ scene = wlr_scene_create(); root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, config.rootcolor); @@ -6176,7 +7069,7 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(c, &clients, link) { // floating window position auto adjust the change of monitor // position - if (c->isfloating && c->mon == m) { + if (c->isfloating && c->mon == m && !is_canvas_layout(m)) { c->geom.x += mon_pos_offsetx; c->geom.y += mon_pos_offsety; c->float_geom = c->geom; @@ -6335,6 +7228,18 @@ void view_in_mon(const Arg *arg, bool want_animation, Monitor *m, toggleseltags: + if (m->canvas_overview_visible) { + m->canvas_overview_visible = false; + m->canvas_overview_closing = false; + } + if (m->minimap_visible) { + m->minimap_visible = false; + if (minimap_scene_tree) { + wlr_scene_node_destroy(&minimap_scene_tree->node); + minimap_scene_tree = NULL; + } + } + if (changefocus) focusclient(focustop(m), 1); arrange(m, want_animation, true); @@ -6566,6 +7471,19 @@ void commitx11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, commmitx11); struct wlr_surface_state *state = &c->surface.xwayland->surface->current; + if (c->mon && is_canvas_layout(c->mon)) { + uint32_t tag = c->mon->pertag->curtag; + float zoom = c->mon->pertag->canvas_zoom[tag]; + float effective_zoom = zoom; + if (c->mon->canvas_in_overview && + c->canvas_geom_backup[tag].width > 0) { + effective_zoom *= (float)c->canvas_geom[tag].width / + c->canvas_geom_backup[tag].width; + } + if (effective_zoom != 1.0f) + apply_visual_zoom(c, effective_zoom); + } + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == (int32_t)state->width && (int32_t)c->geom.height - 2 * (int32_t)c->bw == (int32_t)state->height &&