feat: 为 workspace 添加可用布局列表功能
- wm_workspace_t 新增 available_layouts 和 layout_count 字段 - 新增 WM_COMMAND_CYCLE_LAYOUT 命令支持循环切换布局 - 添加 workspace 布局列表操作 API - 完善命令规则和实现骨架 - 新增布局列表配置示例和文档
This commit is contained in:
@@ -268,6 +268,11 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继
|
|||||||
工作区名称直接存储在 =wm_workspace_t= 中,用于状态栏显示。
|
工作区名称直接存储在 =wm_workspace_t= 中,用于状态栏显示。
|
||||||
核心算法不应依赖此字段。
|
核心算法不应依赖此字段。
|
||||||
|
|
||||||
|
8. =workspace layouts=
|
||||||
|
每个 workspace 维护一个可用布局列表(=available_layouts=),在启动时根据配置分配,运行时固定。
|
||||||
|
workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个)。
|
||||||
|
使用 =CYCLE_LAYOUT= 命令在可用布局间循环切换。
|
||||||
|
|
||||||
* 核心不变量
|
* 核心不变量
|
||||||
|
|
||||||
1. 每个窗口必须且只能属于一个工作区。
|
1. 每个窗口必须且只能属于一个工作区。
|
||||||
@@ -283,6 +288,7 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继
|
|||||||
11. layout 的 name 字段同时用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M")。
|
11. layout 的 name 字段同时用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M")。
|
||||||
12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。
|
12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。
|
||||||
13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。
|
13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。
|
||||||
|
14. 每个 workspace 的可用布局列表在启动时分配,运行时固定,只能通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。
|
||||||
|
|
||||||
* Generation 字段
|
* Generation 字段
|
||||||
|
|
||||||
@@ -365,6 +371,8 @@ Workspace-Output 配置示例和最佳实践。
|
|||||||
=MANAGE_WINDOW=
|
=MANAGE_WINDOW=
|
||||||
=UNMANAGE_WINDOW=
|
=UNMANAGE_WINDOW=
|
||||||
=SWITCH_WORKSPACE=
|
=SWITCH_WORKSPACE=
|
||||||
|
=SET_LAYOUT=
|
||||||
|
=CYCLE_LAYOUT=
|
||||||
=TOGGLE_FLOATING=
|
=TOGGLE_FLOATING=
|
||||||
=TOGGLE_MAXIMIZE=
|
=TOGGLE_MAXIMIZE=
|
||||||
=MOVE_FLOATING_WINDOW=
|
=MOVE_FLOATING_WINDOW=
|
||||||
|
|||||||
@@ -188,6 +188,26 @@
|
|||||||
1. =WM_DIRTY_LAYOUT=
|
1. =WM_DIRTY_LAYOUT=
|
||||||
2. =WM_DIRTY_RENDER=
|
2. =WM_DIRTY_RENDER=
|
||||||
|
|
||||||
|
** =WM_COMMAND_CYCLE_LAYOUT=
|
||||||
|
|
||||||
|
前置条件:
|
||||||
|
|
||||||
|
1. 目标 workspace 存在。
|
||||||
|
2. Workspace 的可用布局列表非空(=layout_count > 0=)。
|
||||||
|
|
||||||
|
状态变化:
|
||||||
|
|
||||||
|
1. 若 =direction == 1= :切换到下一个布局(循环)。
|
||||||
|
2. 若 =direction == -1= :切换到上一个布局(循环)。
|
||||||
|
3. 在 workspace 的 =available_layouts[]= 数组中找到当前 =layout_id= 的索引。
|
||||||
|
4. 按方向移动到相邻索引,循环回到数组开头或末尾。
|
||||||
|
5. 将新的布局 ID 设置为 workspace 的 =layout_id= 。
|
||||||
|
|
||||||
|
副作用:
|
||||||
|
|
||||||
|
1. =WM_DIRTY_LAYOUT=
|
||||||
|
2. =WM_DIRTY_RENDER=
|
||||||
|
|
||||||
* 模式切换命令
|
* 模式切换命令
|
||||||
|
|
||||||
** =WM_COMMAND_TOGGLE_FLOATING=
|
** =WM_COMMAND_TOGGLE_FLOATING=
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ bool wm_policy_apply_command(wm_state_t *state,
|
|||||||
changed = apply_set_layout(state, layouts, command, plan);
|
changed = apply_set_layout(state, layouts, command, plan);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WM_COMMAND_CYCLE_LAYOUT:
|
||||||
|
changed = apply_cycle_layout(state, command, plan);
|
||||||
|
break;
|
||||||
|
|
||||||
case WM_COMMAND_REDRAW:
|
case WM_COMMAND_REDRAW:
|
||||||
changed = apply_redraw(state, command, plan);
|
changed = apply_redraw(state, command, plan);
|
||||||
break;
|
break;
|
||||||
@@ -580,6 +584,45 @@ static bool apply_set_layout(wm_state_t *state,
|
|||||||
}
|
}
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
** =apply_cycle_layout()=
|
||||||
|
|
||||||
|
#+BEGIN_SRC c
|
||||||
|
static bool apply_cycle_layout(wm_state_t *state,
|
||||||
|
const wm_command_t *command,
|
||||||
|
wm_plan_t *plan) {
|
||||||
|
wm_workspace_t *ws =
|
||||||
|
require_workspace(state, command->as.cycle_layout.workspace_id);
|
||||||
|
if (!ws) return false;
|
||||||
|
if (ws->layout_count == 0) return false;
|
||||||
|
|
||||||
|
// 找到当前布局在列表中的索引
|
||||||
|
size_t current_idx = 0;
|
||||||
|
for (size_t i = 0; i < ws->layout_count; i++) {
|
||||||
|
if (ws->available_layouts[i] == ws->layout_id) {
|
||||||
|
current_idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算下一个索引(支持前后循环)
|
||||||
|
size_t next_idx;
|
||||||
|
if (command->as.cycle_layout.direction > 0) {
|
||||||
|
next_idx = (current_idx + 1) % ws->layout_count;
|
||||||
|
} else {
|
||||||
|
next_idx = (current_idx == 0) ? (ws->layout_count - 1) : (current_idx - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果新旧布局相同,直接返回
|
||||||
|
wm_layout_id_t next_layout = ws->available_layouts[next_idx];
|
||||||
|
if (next_layout == ws->layout_id) return false;
|
||||||
|
|
||||||
|
ws->layout_id = next_layout;
|
||||||
|
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||||
|
push_render_all_outputs(state, plan);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
** =apply_redraw()=
|
** =apply_redraw()=
|
||||||
|
|
||||||
#+BEGIN_SRC c
|
#+BEGIN_SRC c
|
||||||
|
|||||||
@@ -212,9 +212,75 @@ bool load_workspace_config(const char *path,
|
|||||||
-- 如果 workspace 4 当前在 DP-2 显示,窗口会在 DP-2 出现
|
-- 如果 workspace 4 当前在 DP-2 显示,窗口会在 DP-2 出现
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
** 布局列表配置
|
||||||
|
|
||||||
|
每个 workspace 可以配置自己的可用布局列表。
|
||||||
|
|
||||||
|
*** 基础配置
|
||||||
|
|
||||||
|
#+BEGIN_SRC lua
|
||||||
|
-- 声明布局注册表
|
||||||
|
layouts = {
|
||||||
|
{ id = 0, name = "T", fn = layout_tile },
|
||||||
|
{ id = 1, name = "M", fn = layout_monocle },
|
||||||
|
{ id = 2, name = "F", fn = layout_floating },
|
||||||
|
{ id = 3, name = "S", fn = layout_stack },
|
||||||
|
{ id = 4, name = "G", fn = layout_grid },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 单显示器:每个 workspace 可用所有布局
|
||||||
|
workspaces = {
|
||||||
|
{ id = 1, name = "1", output_id = 0, layouts = {0, 1, 2, 3, 4}, default_layout = 0 },
|
||||||
|
{ id = 2, name = "2", output_id = 0, layouts = {0, 1, 2, 3, 4}, default_layout = 0 },
|
||||||
|
{ id = 3, name = "3", output_id = 0, layouts = {0, 1, 2, 3, 4}, default_layout = 0 },
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** 主题化布局配置
|
||||||
|
|
||||||
|
不同用途的 workspace 配置不同的布局集合:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lua
|
||||||
|
workspaces = {
|
||||||
|
-- 代码 workspace:平铺、单列、堆叠(适合代码+终端)
|
||||||
|
{ id = 1, name = "code", output_id = 0,
|
||||||
|
layouts = {0, 3, 4}, default_layout = 0 }, -- T, S, G
|
||||||
|
|
||||||
|
-- 浏览器 workspace:单列、浮动、全屏(适合阅读)
|
||||||
|
{ id = 2, name = "web", output_id = 0,
|
||||||
|
layouts = {1, 2}, default_layout = 1 }, -- M, F
|
||||||
|
|
||||||
|
-- 游戏 workspace:仅浮动(游戏窗口通常需要浮动)
|
||||||
|
{ id = 3, name = "game", output_id = 1,
|
||||||
|
layouts = {2}, default_layout = 2 }, -- F only
|
||||||
|
|
||||||
|
-- 聊天 workspace:堆叠、单列
|
||||||
|
{ id = 4, name = "chat", output_id = 1,
|
||||||
|
layouts = {3, 1}, default_layout = 3 }, -- S, M
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** 默认布局配置
|
||||||
|
|
||||||
|
如果不指定 layouts 字段,使用全局默认布局列表:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lua
|
||||||
|
-- 全局默认
|
||||||
|
default_layouts = {0, 1, 2, 3, 4} -- 所有布局可用
|
||||||
|
|
||||||
|
-- workspace 省略 layouts 时使用全局默认
|
||||||
|
workspaces = {
|
||||||
|
{ id = 1, name = "1", output_id = 0 }, -- 使用全局默认
|
||||||
|
{ id = 2, name = "2", output_id = 0,
|
||||||
|
layouts = {0, 1}, default_layout = 0 }, -- 自定义
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
** 注意事项
|
** 注意事项
|
||||||
|
|
||||||
1. *固定归属*:workspace.output_id 在创建后不应改变
|
1. *固定归属*:workspace.output_id 在创建后不应改变
|
||||||
2. *输出变化*:显示器配置变化时,WM 会重启,重新扫描和配置
|
2. *输出变化*:显示器配置变化时,WM 会重启,重新扫描和配置
|
||||||
3. *可见性*:窗口只在归属的 output 上可见
|
3. *可见性*:窗口只在归属的 output 上可见
|
||||||
4. *独立性*:切换不同 output 的 workspace 互不影响
|
4. *独立性*:切换不同 output 的 workspace 互不影响
|
||||||
|
5. *布局列表*:workspace.layouts 在启动时分配,运行时固定
|
||||||
|
6. *布局切换*:使用 CYCLE_LAYOUT 命令在可用布局间循环切换
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ typedef enum wm_command_type_t {
|
|||||||
WM_COMMAND_MOVE_FLOATING_WINDOW,
|
WM_COMMAND_MOVE_FLOATING_WINDOW,
|
||||||
WM_COMMAND_RESIZE_FLOATING_WINDOW,
|
WM_COMMAND_RESIZE_FLOATING_WINDOW,
|
||||||
WM_COMMAND_SET_LAYOUT,
|
WM_COMMAND_SET_LAYOUT,
|
||||||
|
WM_COMMAND_CYCLE_LAYOUT,
|
||||||
WM_COMMAND_REDRAW,
|
WM_COMMAND_REDRAW,
|
||||||
WM_COMMAND_QUIT,
|
WM_COMMAND_QUIT,
|
||||||
} wm_command_type_t;
|
} wm_command_type_t;
|
||||||
@@ -133,5 +134,10 @@ typedef struct wm_command_t {
|
|||||||
wm_workspace_id_t workspace_id;
|
wm_workspace_id_t workspace_id;
|
||||||
wm_layout_id_t layout_id;
|
wm_layout_id_t layout_id;
|
||||||
} set_layout;
|
} set_layout;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
wm_workspace_id_t workspace_id;
|
||||||
|
int direction; // 1: 下一个布局, -1: 上一个布局
|
||||||
|
} cycle_layout;
|
||||||
} as;
|
} as;
|
||||||
} wm_command_t;
|
} wm_command_t;
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ typedef struct wm_window_t {
|
|||||||
typedef struct wm_workspace_t {
|
typedef struct wm_workspace_t {
|
||||||
wm_workspace_id_t id;
|
wm_workspace_id_t id;
|
||||||
wm_output_id_t output_id; // 固定归属某个输出
|
wm_output_id_t output_id; // 固定归属某个输出
|
||||||
wm_layout_id_t layout_id;
|
wm_layout_id_t layout_id; // 当前活动布局(可用布局列表中的一个)
|
||||||
wm_window_id_t focused_window_id;
|
wm_window_id_t focused_window_id;
|
||||||
|
|
||||||
|
// 可用布局列表(启动时根据配置分配,运行时固定)
|
||||||
|
wm_layout_id_t *available_layouts;
|
||||||
|
size_t layout_count;
|
||||||
|
|
||||||
// 名称(核心算法不依赖,仅用于状态栏显示)
|
// 名称(核心算法不依赖,仅用于状态栏显示)
|
||||||
char *name;
|
char *name;
|
||||||
} wm_workspace_t;
|
} wm_workspace_t;
|
||||||
@@ -91,6 +95,37 @@ size_t wm_state_workspace_get_by_output(const wm_state_t *state,
|
|||||||
wm_output_id_t output_id,
|
wm_output_id_t output_id,
|
||||||
wm_workspace_t **out_workspaces);
|
wm_workspace_t **out_workspaces);
|
||||||
|
|
||||||
|
// ========== Workspace 布局操作 ==========
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 设置 workspace 的可用布局列表
|
||||||
|
* 注意:应在 workspace 初始化时调用,运行时不应修改
|
||||||
|
*/
|
||||||
|
void wm_workspace_set_layouts(wm_workspace_t *workspace,
|
||||||
|
const wm_layout_id_t *layouts,
|
||||||
|
size_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 获取 workspace 的可用布局列表
|
||||||
|
*/
|
||||||
|
const wm_layout_id_t *wm_workspace_get_layouts(const wm_workspace_t *workspace,
|
||||||
|
size_t *out_count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 切换到下一个布局(循环)
|
||||||
|
*/
|
||||||
|
bool wm_workspace_cycle_layout(wm_workspace_t *workspace);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 切换到指定索引的布局
|
||||||
|
*/
|
||||||
|
bool wm_workspace_set_layout_by_index(wm_workspace_t *workspace, size_t index);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 切换到指定 ID 的布局(如果该布局在可用列表中)
|
||||||
|
*/
|
||||||
|
bool wm_workspace_set_layout_by_id(wm_workspace_t *workspace, wm_layout_id_t id);
|
||||||
|
|
||||||
// ========== Output 访问 ==========
|
// ========== Output 访问 ==========
|
||||||
|
|
||||||
wm_output_t *wm_state_output(wm_state_t *state, wm_output_id_t id);
|
wm_output_t *wm_state_output(wm_state_t *state, wm_output_id_t id);
|
||||||
|
|||||||
Reference in New Issue
Block a user