feat: add canvas layout

This commit is contained in:
ernestoCruz05 2026-03-21 14:20:55 +00:00
parent f94ddc671e
commit 9e2f0fc2b8
13 changed files with 1509 additions and 127 deletions

View file

@ -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,

View file

@ -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
```
---

View file

@ -1,5 +1,5 @@
project('mango', ['c', 'cpp'],
version : '0.12.7',
version : '0.12.8',
)
subdir('protocols')

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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,

View file

@ -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)

View file

@ -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) {

146
src/layout/canvas.h Normal file
View file

@ -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);
}

View file

@ -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
};

File diff suppressed because it is too large Load diff