Skip to main content
scroll实现grid布局
  1. Blog/

scroll实现grid布局

· loading · loading · ·
IT IT Linux Guide
Table of Contents

太长不看版本
#

local args, state = ...
local scroll = require("scroll")


local debug_notify = function(msg)
	scroll.command(nil, 'exec notify-send "' .. msg .. '"')
end


local column_limit = tonumber(args[2]) or 3
local fit_size = tonumber(args[3]) or 0
local grid_rows = tonumber(args[4]) or column_limit
local grid_columns = tonumber(args[5]) or grid_rows
local grid = scroll.state_get_value(state, "grid_state")

if grid == nil then
	scroll.state_set_value(state, "grid_state", {
		map_id = nil,
		active_workspaces = {},
		last_view = nil,
	})
	grid = scroll.state_get_value(state, "grid_state")
end


local function on_destroy(view, _)
    local workspace = scroll.focused_workspace()
    if not workspace then return end
    
    local ws_name = scroll.workspace_get_name(workspace)
    local tiling = scroll.workspace_get_tiling(workspace)
    if not grid["active_workspaces"][ws_name] then return end
	local children = scroll.container_get_children(tiling[1])

    if #tiling==1 and #children == 1 then
        grid["active_workspaces"][ws_name] = nil
        scroll.workspace_set_mode(workspace, { insert = "after", focus = true })
    end
end
local function on_create_view(view, _)
	local focused_view = scroll.focused_view()
	local workspace = scroll.focused_workspace()
	if not workspace then
		return
	end

	local tiling = scroll.workspace_get_tiling(workspace)
	local current_ws_name = scroll.workspace_get_name(workspace)
	grid["last_view"] = focused_view

	-- Check if Grid Mode is active for this workspace
	local ws_config = grid["active_workspaces"][current_ws_name]
	if not ws_config then
		return
	end

	local container = scroll.view_get_container(view)

	if #tiling > 1 then
		local target_tiling = nil
		-- Iterate through existing columns to find a gap based on column_limit
		for i = 1, #tiling - 1 do
			local prev_container = tiling[i]
			local children = scroll.container_get_children(prev_container)
			if children and #children < ws_config[1] then
				target_tiling = i
				break
			end
		end

		if target_tiling then
			local steps = (#tiling - target_tiling - 1) * 2 + 1
			for i = 1, steps do
				scroll.command(container, "move left nomode")
			end
		end
	end

	if ws_config[4] == 1 then
		scroll.command(container, "set_size h " .. (1 / ws_config[3]))
		scroll.command(container, "set_size v " .. (1 / ws_config[2]))
	end

	if focused_view then
		local focused_con = scroll.view_get_container(focused_view)
		scroll.container_set_focus(focused_con)
	end
	if ws_config[4] == 1 and #tiling > 1 then
		scroll.command(nil, "exec scrollmsg lua ~/.config/scroll/scripts/focus_back.lua toggle")
	end
end

local workspace = scroll.focused_workspace()
if not workspace then
	return
end
local ws_name = scroll.workspace_get_name(workspace)

local function ensure_callback()
    if not grid["map_id"] then
        grid["map_id"] = scroll.add_callback("view_map", on_create_view, nil)
    end
    if not grid["unmap_id"] then
        grid["unmap_id"] = scroll.add_callback("view_unmap", on_destroy, nil)
    end
end

if args[1] == "toggle" then
	if grid["active_workspaces"][ws_name] then
		grid["active_workspaces"][ws_name] = nil
		scroll.workspace_set_mode(workspace, { insert = "after", focus = true })
		debug_notify("Grid Mode: DISABLED for [" .. ws_name .. "]")
	else
		grid["active_workspaces"][ws_name] = { column_limit, grid_rows, grid_columns, fit_size }
		scroll.workspace_set_mode(workspace, { insert = "end", focus = true })
		debug_notify(
			string.format(
				"Grid Mode: ENABLED for %s (Lim:%d, H:%d, V:%d)",
				ws_name,
				column_limit,
				grid_rows,
				grid_columns
			)
		)
	end
elseif args[1] == "disable" then
	grid["active_workspaces"][ws_name] = nil
	scroll.workspace_set_mode(workspace, { insert = "after", focus = true })
	debug_notify("Grid Mode: DISABLED for [" .. ws_name .. "]")
elseif args[1] == "enable" then
	grid["active_workspaces"][ws_name] = { column_limit, grid_rows, grid_columns, fit_size }
	scroll.workspace_set_mode(workspace, { insert = "end", focus = true })
	debug_notify(
		string.format("Grid Mode: ENABLED for %s (Lim:%d, H:%d, V:%d)", ws_name, column_limit, grid_rows, grid_columns)
	)
end
ensure_callback()

scroll介绍
#

由于 Hyprland 最近一次破坏性更新(Break Update)带来的不便,在习惯了 hyprscroller 的操作逻辑后,我选择了由同一作者维护的新项目:scroll。scroll 提供了更加灵活的“卷轴”体验,支持上下左右全方位的平铺与滚动。体验很舒服,但是grid模式没有了,没法愉快的九宫格刷b站了,幸好作者提供了丰富的Lua接口,实在不行咱们可以自己实现一个。

初步实现
#

说干就干,询问作者得到了个 初步的例子,核心逻辑:将新窗口的插入位置固定在工作区的最后,脚本会检查它的前一列容器中的子窗口数量。如果数量小于 3,则执行一次 move left,将新窗口塞进前一列。

遇到问题及优化
#

很容易发现我们的初步脚本存在以下问题

  • 只能实现按顺序的移动, 如果中间有窗口关闭则会继续从最右边重新开始,体验上很怪。

寻坑-加入的逻辑

加入工作区记忆,调整窗口大小,持久化存储
#