From 57c6a831d44028003dab35c69197dc42e730397c Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Thu, 12 Mar 2026 06:56:56 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=98=8E=E7=A1=AE=20sticky=20=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E8=A7=84=E5=88=99=E5=B9=B6=E4=BF=AE=E6=AD=A3=20core?= =?UTF-8?q?=20=E5=A4=B4=E6=96=87=E4=BB=B6=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/min_core_draft/README.org | 18 +- docs/min_core_draft/WM_COMMAND_RULES.org | 49 +++-- .../WM_POLICY_APPLY_COMMAND_SKELETON.org | 48 ++++- .../min_core_draft/WM_STICKY_WINDOW_RULES.org | 200 ++++++++++++++++++ src/core/runtime.h | 2 + src/core/state.h | 1 - 6 files changed, 287 insertions(+), 31 deletions(-) create mode 100644 docs/min_core_draft/WM_STICKY_WINDOW_RULES.org diff --git a/docs/min_core_draft/README.org b/docs/min_core_draft/README.org index 39999d1..99edb06 100644 --- a/docs/min_core_draft/README.org +++ b/docs/min_core_draft/README.org @@ -300,10 +300,10 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个 5. =geometry_mode= 只负责几何模式,不负责 z-order。 6. =stack_order[]= 只负责 z-order,不负责几何模式。 7. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。 -8. =sticky= 只影响可见性,不改变窗口归属工作区。 -9. floating 窗口可以跨多个输出显示,但归属工作区仍然只有一个。 -10. 多输出下 floating 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。 -11. layout 的 name 字段同时用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M")。 +8. =sticky => floating= ;=sticky= 只放宽 workspace 可见性,不改变窗口归属工作区。 +9. floating / sticky 窗口都可以跨多个输出显示,但归属工作区仍然只有一个。 +10. 多输出下 floating / sticky 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。 +11. layout 的 =symbol= 字段用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M");=name= 应保持稳定、可读(如 "tile", "monocle")。 12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。 13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。 14. 每个 workspace 的可用布局列表在启动时分配,运行时固定,只能通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。 @@ -347,7 +347,8 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个 4. =wm_command.h= 强类型语义命令。 -命令语义补充见 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 。 +命令语义补充见 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] ; +=sticky= 多显示器与跨屏行为补充见 [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。 5. =wm_plan.h= 脏标记和后端副作用。 @@ -373,10 +374,13 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个 12. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 关键命令的前置条件、状态转移、可见性和副作用规则。 -13. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]] +13. [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] +=sticky= 窗口在 workspace、taskbar、多显示器和跨 output 拖动下的补充规则。 + +14. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]] =wm_policy_apply_command()= 的伪代码骨架和实现顺序建议。 -14. [[file:WORKSPACE_CONFIG_EXAMPLES.org][WORKSPACE_CONFIG_EXAMPLES.org]] +15. [[file:WORKSPACE_CONFIG_EXAMPLES.org][WORKSPACE_CONFIG_EXAMPLES.org]] Workspace-Output 配置示例和最佳实践。 * 下一步实施顺序 diff --git a/docs/min_core_draft/WM_COMMAND_RULES.org b/docs/min_core_draft/WM_COMMAND_RULES.org index 734292d..483ac8a 100644 --- a/docs/min_core_draft/WM_COMMAND_RULES.org +++ b/docs/min_core_draft/WM_COMMAND_RULES.org @@ -2,6 +2,9 @@ 这份文档定义 =wm_policy_apply_command()= 应遵守的最小语义。目标不是覆盖所有未来扩展,而是先把最关键的状态转移规则固定下来,避免后续实现时在 =floating=、=sticky=、=geometry_mode= 和 =stack_order[]= 上出现分叉语义。 +=sticky= 在多显示器、taskbar 和跨 output 拖动下的补充语义见 +[[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。 + * 通用规则 1. 找不到目标 =window_id=、=workspace_id= 或 =output_id= 时,命令失败,不修改状态。 @@ -9,13 +12,14 @@ 3. 命令成功且造成状态变化时,递增 =state.generation= ,并设置相应 =dirty_flags= 。 4. =stack_order[]= 是全局 z-order 真相,=raise/lower/focus= 只能通过修改这个数组影响堆叠。 5. =geometry_mode= 只表示几何显示模式,不表示窗口是否在最上层。 -6. =sticky= 只影响可见性,不改变 =workspace_id= 。 -7. =floating= 决定基础几何来源: +6. =sticky => floating= 。 +7. =sticky= 只放宽 workspace 可见性,不改变 =workspace_id= 。 +8. =floating= 决定基础几何来源: =floating = false= 时,基础几何来自 layout 结果。 =floating = true= 时,基础几何来自 =float_rect= 。 -8. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。 -9. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。 -10. =border_width= 不是 =wm_window_t= 真状态;runtime 在提交 +9. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。 +10. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。 +11. =border_width= 不是 =wm_window_t= 真状态;runtime 在提交 =WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。 * 管理类命令 @@ -32,9 +36,10 @@ 1. 向 =state.windows[]= 添加一个新窗口。 2. 设定其 =workspace_id= 。 3. 初始 =floating= 、=sticky= 、=urgent= 、=geometry_mode= 由 =command.as.manage_window.initial_state= 显式给出。 -4. 若 =has_initial_float_rect == true= ,将其复制到 =float_rect= 。 -5. 将窗口追加到 =stack_order[]= 顶部。 -6. 是否将该窗口设为所属 workspace 的 =focused_window_id= ,由 =initial_state.set_focus= 或 =policy.manage_sets_focus= 决定。 +4. 若 =initial_state.sticky == true= ,则结果状态中必须满足 =floating == true= 。 +5. 若 =has_initial_float_rect == true= ,将其复制到 =float_rect= 。 +6. 将窗口追加到 =stack_order[]= 顶部。 +7. 是否将该窗口设为所属 workspace 的 =focused_window_id= ,由 =initial_state.set_focus= 或 =policy.manage_sets_focus= 决定。 副作用: @@ -215,6 +220,11 @@ ** =WM_COMMAND_TOGGLE_FLOATING= +前置条件: + +1. 目标窗口存在。 +2. 若目标窗口当前 =sticky == true= ,命令失败。 + 状态变化: 1. 若原来 =floating == false= : @@ -235,10 +245,14 @@ 状态变化: -1. 切换目标窗口的 =sticky= 状态。 -2. 不改变 =workspace_id= 。 -3. 不改变 =floating= 。 -4. 不改变 =geometry_mode= 。 +1. 若原来 =sticky == false= : + 若当前 =floating == false= ,先将当前 =frame_rect= 复制到 =float_rect= ,再设 =floating = true= 。 + 然后将 =sticky= 设为 =true= 。 +2. 若原来 =sticky == true= : + 将 =sticky= 设为 =false= 。 +3. 不改变 =workspace_id= 。 +4. 关闭 =sticky= 时不自动恢复平铺;=floating= 保持原值不变。 +5. 不改变 =geometry_mode= 。 副作用: @@ -318,6 +332,7 @@ 3. 若 =commit_workspace_change == true= : 依据 =anchor= 找到目标输出。 再按 =policy= 决定是否修改 =workspace_id= 。 + 该规则对普通 =floating= 窗口和 =sticky= 窗口一致适用。 4. 不改变 =geometry_mode= 。 5. =float_rect= 是外框矩形,因此移动的是包含边框后的最终窗口外框。 @@ -335,7 +350,7 @@ 状态变化: -1. 按 =dw/dh= 修改 =float_rect.w/h= 。 +1. 按 =dw/dh= 修改 =float_rect.width/height= 。 2. 应对宽高做最小值约束,避免得到零或负值。 3. 不改变 =workspace_id= 。 4. 不改变 =geometry_mode= 。 @@ -414,11 +429,15 @@ bool wm_state_window_should_be_visible_on_output(const wm_state_t *state, const wm_window_t *window, const wm_output_t *output) { if (!wm_state_window_should_be_visible(state, window)) return false; - if (window->sticky) return true; - return window->workspace_id == output->current_workspace_id; + return wm_rect_intersects(window->frame_rect, output->geometry); } #+END_SRC +若后续存在按 output 渲染的 taskbar ,建议在下列条件同时满足时显示窗口条目: + +1. =window->skip_taskbar == false= +2. =wm_state_window_should_be_visible_on_output(...) == true= + * 几何解析优先级 窗口最终矩形建议按下面的优先级解析: diff --git a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org index aa303fa..d3fffbb 100644 --- a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org +++ b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org @@ -2,6 +2,9 @@ 这份文档给出 =wm_policy_apply_command()= 的最小实现骨架。目标是把实际代码的控制流固定住,使其和 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 中定义的命令规则一一对应。 +=sticky= 在多显示器、taskbar 和跨 output 拖动下的补充语义见 +[[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。 + * 函数职责 =wm_policy_apply_command()= 只做四件事: @@ -187,6 +190,10 @@ static bool apply_manage_window(wm_state_t *state, .urgent = command->as.manage_window.initial_state.urgent, }; + if (win.sticky) { + win.floating = true; + } + if (command->as.manage_window.has_initial_float_rect) { win.float_rect = command->as.manage_window.initial_float_rect; } @@ -300,13 +307,26 @@ static bool apply_toggle_sticky(wm_state_t *state, wm_window_t *win = require_window(state, command->as.toggle_sticky.window_id); if (!win) return false; - win->sticky = !win->sticky; + if (!win->sticky) { + if (!win->floating) { + win->float_rect = win->frame_rect; + win->floating = true; + } + win->sticky = true; + } else { + win->sticky = false; + /* 关闭 sticky 时保持 floating 不变,不自动恢复平铺。 */ + } + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; push_render_all_outputs(state, plan); return true; } #+END_SRC +实际实现中,若关闭 =sticky= 后窗口对当前正在查看的 workspace 不再可见, +且它曾是相关 workspace 的焦点窗口,则应重选该 workspace 的焦点。 + ** =apply_focus_direction()= #+BEGIN_SRC c @@ -438,6 +458,7 @@ static bool apply_toggle_floating(wm_state_t *state, wm_plan_t *plan) { wm_window_t *win = require_window(state, command->as.toggle_floating.window_id); if (!win) return false; + if (win->sticky) return false; if (!win->floating) { win->floating = true; @@ -529,6 +550,8 @@ static bool apply_move_floating_window(wm_state_t *state, require_window(state, command->as.move_floating_window.window_id); if (!win || !win->floating) return false; + wm_workspace_id_t old_ws_id = win->workspace_id; + win->float_rect.x += command->as.move_floating_window.dx; win->float_rect.y += command->as.move_floating_window.dy; @@ -539,6 +562,12 @@ static bool apply_move_floating_window(wm_state_t *state, command->as.move_floating_window.policy == WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE) { win->workspace_id = out->current_workspace_id; + if (win->workspace_id != old_ws_id) { + wm_workspace_t *src = require_workspace(state, old_ws_id); + if (src && src->focused_window_id == win->id) { + maybe_refocus_workspace(state, src); + } + } } } @@ -558,12 +587,12 @@ static bool apply_resize_floating_window(wm_state_t *state, require_window(state, command->as.resize_floating_window.window_id); if (!win || !win->floating) return false; - win->float_rect.w = - clamp_u16((int)win->float_rect.w + command->as.resize_floating_window.dw, - WM_MIN_WINDOW_W, UINT16_MAX); - win->float_rect.h = - clamp_u16((int)win->float_rect.h + command->as.resize_floating_window.dh, - WM_MIN_WINDOW_H, UINT16_MAX); + win->float_rect.width = + clamp_i32(win->float_rect.width + command->as.resize_floating_window.dw, + WM_MIN_WINDOW_WIDTH, INT32_MAX); + win->float_rect.height = + clamp_i32(win->float_rect.height + command->as.resize_floating_window.dh, + WM_MIN_WINDOW_HEIGHT, INT32_MAX); plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; push_render_all_outputs(state, plan); @@ -581,7 +610,9 @@ static bool apply_set_layout(wm_state_t *state, wm_workspace_t *ws = require_workspace(state, command->as.set_layout.workspace_id); if (!ws) return false; - if (!wm_layout_lookup(layouts, command->as.set_layout.layout_id)) return false; + if (!wm_layout_slot_get(layouts, command->as.set_layout.layout_id)) { + return false; + } if (ws->layout_id == command->as.set_layout.layout_id) return false; ws->layout_id = command->as.set_layout.layout_id; @@ -663,6 +694,7 @@ static bool apply_quit(wm_state_t *state, 1. 如果有 =WM_DIRTY_LAYOUT= ,则对每个 output 的当前 workspace 重新计算 layout。 2. 根据窗口的 =geometry_mode= 、=floating= 和 layout 结果解析最终矩形。 + =sticky= 窗口由于满足 =sticky => floating= ,因此同样走 =float_rect= 路径。 3. 生成 =WM_EFFECT_CONFIGURE_WINDOW= 。 4. 若有 =WM_DIRTY_STACK= ,生成 =WM_EFFECT_RESTACK_WINDOWS= 。 5. 若有 =WM_DIRTY_RENDER= ,生成 =WM_EFFECT_RENDER_OUTPUT= 。 diff --git a/docs/min_core_draft/WM_STICKY_WINDOW_RULES.org b/docs/min_core_draft/WM_STICKY_WINDOW_RULES.org new file mode 100644 index 0000000..dd70b34 --- /dev/null +++ b/docs/min_core_draft/WM_STICKY_WINDOW_RULES.org @@ -0,0 +1,200 @@ +* Sticky 窗口规则 + +这份文档补充 =sticky= 窗口在最小核心中的约束,重点覆盖以下问题: + +1. =sticky= 与 =floating= 的关系 +2. =sticky= 窗口是否参与自动布局 +3. =sticky= 窗口在 taskbar 中的显示规则 +4. 多显示器下 =sticky= 窗口的可见性与跨屏移动语义 + +* 核心结论 + +1. =sticky => floating= +2. =sticky= 窗口不参与任何自动布局 +3. =sticky= 只放宽 workspace 可见性,不改变窗口的唯一归属 =workspace_id= +4. =sticky= 窗口不复制到每个 output;是否出现在某个 output 上,只由其最终矩形是否覆盖该 output 决定 +5. 拖动 =sticky= 或普通 =floating= 窗口跨 output 时,默认只修改 =float_rect= ,不隐式修改 =workspace_id= +6. 只有显式提交跨 output 迁移时,才允许把窗口归属 workspace 改到目标 output 当前显示的 workspace + +* 状态约束 + +** =sticky= 与 =floating= + +最小核心中,=sticky= 不是独立于 =floating= 的另一套几何模式,而是附加在 floating 语义之上的可见性扩展。 + +因此规定: + +1. 若窗口进入 =sticky= 状态,则必须满足 =floating == true= +2. 若窗口原本是平铺窗口,开启 =sticky= 时,应先把当前最终外框矩形写入 =float_rect= ,再设 =floating = true= ,最后设 =sticky = true= +3. 关闭 =sticky= 时,只清除 =sticky= 标记,不自动恢复到平铺模式 + +不自动恢复平铺的原因是:否则核心必须额外保存“进入 sticky 前是否为 floating”的历史状态,复杂度不值得。 + +** 唯一归属 workspace + +每个窗口始终只有一个归属 =workspace_id= 。 + +=sticky= 的作用只是: + +- 让窗口在非归属 workspace 上也可见 + +而不是: + +- 把窗口复制到多个 workspace +- 让窗口同时归属于多个 workspace + +因此: + +1. =workspace_id= 仍然是窗口的唯一归属 +2. layout、focus 历史、命令目标和后续迁移规则都仍以该归属 workspace 为准 + +* 布局规则 + +** 不参与自动布局 + +=sticky= 窗口不参与任何自动布局。 + +这条规则在单显示器和多显示器下都成立: + +1. 不进入任何 workspace 的平铺输入 +2. 不进入任何 output 的布局输入 +3. 几何始终来自 =float_rect= + +原因: + +1. 自动布局应是 workspace-local / output-local 的 +2. =sticky= 如果参与布局,会让同一个窗口对多个 workspace 或多个 output 的布局结果产生耦合 +3. 同一个窗口不能同时接受多份独立 layout 结果 + +** =fn == NULL= 的 floating 布局槽位 + +若当前 workspace 的活动布局槽位满足 =fn == NULL= ,则该布局表示 =floating= 布局。 + +这只表示当前 workspace 上的普通窗口不进行平铺计算,不改变 =sticky= 窗口的特殊规则: + +1. 普通 floating 窗口:几何来自各自的 =float_rect= +2. =sticky= 窗口:同样几何来自 =float_rect= +3. 二者都不参与 layout 函数计算 + +* Taskbar 规则 + +** 基本规则 + +如果 taskbar 表达的是“当前 workspace 上用户可见的窗口列表”,则 =sticky= 窗口应被视为可见窗口,因此应出现在 taskbar 中。 + +推荐的基础判断是: + +1. =skip_taskbar == false= +2. 窗口在当前上下文中实际可见 + +** 单显示器 + +单显示器下,若当前 workspace 与窗口归属 workspace 不同,只要窗口是 =sticky= 且当前几何可见,仍应出现在 taskbar 中。 + +** 多显示器 / 每 output 一条 taskbar + +若系统为每个 output 提供独立 taskbar ,推荐规则是: + +1. 只在窗口实际可见的 output 上显示该窗口 +2. 不在所有 output 的 taskbar 中无条件复制显示 + +也就是说: + +1. 窗口只落在左屏,则只显示在左屏 taskbar +2. 窗口只落在右屏,则只显示在右屏 taskbar +3. 窗口跨越两个 output ,则可同时出现在两个 output 的 taskbar + +这样 taskbar 与屏幕上的真实可见内容保持一致。 + +* 多显示器规则 + +** 坐标空间 + +=float_rect= 和 =frame_rect= 使用全局桌面坐标,而不是某个 output 的局部坐标。 + +因此: + +1. 一个 floating/sticky 窗口可以自然跨越多个 output +2. output 可见性可以通过“窗口最终矩形是否与 output 矩形相交”来判断 + +** 可见性 + +=sticky= 只放宽 workspace 可见性,不提供 output 复制语义。 + +因此: + +1. 窗口不会因为 =sticky= 自动在所有 output 上都出现 +2. 它只会出现在最终矩形实际覆盖到的 output 上 +3. 若窗口完全移到另一块屏幕,则旧屏幕上不再可见 + +* 跨 output 移动规则 + +** 拖动过程中 + +无论窗口是普通 =floating= 还是 =sticky= ,拖动过程中都只更新 =float_rect= 。 + +不应在拖动过程中频繁修改 =workspace_id= ,否则归属语义会抖动,也不利于测试。 + +** 拖动结束后的归属处理 + +拖动结束后,是否迁移窗口归属 workspace,应由显式策略控制,而不是隐式发生。 + +推荐沿用以下策略: + +1. =WM_CROSS_OUTPUT_KEEP_WORKSPACE= + - 只更新 =float_rect= + - 不修改 =workspace_id= +2. =WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE= + - 使用稳定锚点确定目标 output + - 若锚点落在另一个 output 上,则把 =workspace_id= 改为该 output 当前显示的 workspace + +推荐锚点: + +1. 窗口中心点 +2. 或命令中显式提供的 =anchor= + +不建议按覆盖面积决定归属,因为该规则在窗口跨屏时不稳定,也不直观。 + +** =sticky= 下的跨 output 移动 + +=sticky= 窗口跨屏拖动时,默认也不自动修改 =workspace_id= 。 + +原因: + +1. =sticky= 本来就可以跨 workspace 可见 +2. 若移动几何时顺便修改归属,会让“移动窗口”和“迁移窗口”两个动作发生耦合 + +因此推荐: + +1. 默认只更新 =float_rect= +2. 若用户明确要求迁移,则再执行归属 workspace 切换 + +* 关闭 Sticky 的语义 + +关闭 =sticky= 时: + +1. 只清除 =sticky= 标记 +2. 不自动修改 =workspace_id= +3. 不自动修改 =floating= +4. 不自动恢复平铺 + +若当前查看的 workspace 不是窗口归属的 =workspace_id= ,则关闭 =sticky= 后窗口应立即从当前视图消失。 + +如果该窗口在关闭 =sticky= 前是当前焦点窗口,则需要重新选择当前 workspace 的焦点窗口。 + +* 设计原则总结 + +这套规则的目标是把几个概念彻底拆开: + +1. =floating= 管几何来源 +2. =sticky= 管跨 workspace 可见性 +3. =workspace_id= 管唯一归属 +4. 跨 output 拖动只改几何 +5. workspace 迁移必须显式提交 + +这样可以避免以下几类混乱语义: + +1. 拖动窗口时归属 workspace 频繁跳变 +2. =sticky= 窗口同时参与多个布局 +3. taskbar 显示内容与实际可见窗口不一致 +4. 关闭 =sticky= 时隐式触发额外状态回滚 diff --git a/src/core/runtime.h b/src/core/runtime.h index c969d22..f02e5bb 100644 --- a/src/core/runtime.h +++ b/src/core/runtime.h @@ -1,3 +1,5 @@ +#pragma once + #include "core/state.h" typedef struct wm_runtime_t { diff --git a/src/core/state.h b/src/core/state.h index e22e27a..f4cac41 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -50,7 +50,6 @@ typedef struct wm_output_t { } wm_output_t; typedef struct wm_state_t { - /* 内部实现(不透明结构体,实现文件中定义) */ wm_workspace_t *workspaces; size_t workspace_count; size_t workspace_capacity;