diff --git a/editor.lua b/editor.lua index 175ad54..44bc78f 100644 --- a/editor.lua +++ b/editor.lua @@ -15,46 +15,46 @@ local INFO = 0 local DEBUG = -1 local module = { - log_level = WARNING, - nested_layouts = { - ["0"] = awful.layout.suit.tile, - ["1"] = awful.layout.suit.spiral, - ["2"] = awful.layout.suit.fair, - ["3"] = awful.layout.suit.fair.horizontal, - }, + log_level = WARNING, + nested_layouts = { + ["0"] = awful.layout.suit.tile, + ["1"] = awful.layout.suit.spiral, + ["2"] = awful.layout.suit.fair, + ["3"] = awful.layout.suit.fair.horizontal, + }, } local function log(level, msg) - if level > module.log_level then - print(msg) - end + if level > module.log_level then + print(msg) + end end local function with_alpha(col, alpha) - local r, g, b - _, r, g, b, _ = col:get_rgba() - return lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha) + local r, g, b + _, r, g, b, _ = col:get_rgba() + return lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha) end local function max(a, b) - if a < b then return b else return a end + if a < b then return b else return a end end local function is_tiling(c) - return - not (c.tomb_floating or c.floating or c.maximized_horizontal or c.maximized_vertical or c.maximized or c.fullscreen) + return + not (c.tomb_floating or c.floating or c.maximized_horizontal or c.maximized_vertical or c.maximized or c.fullscreen) end local function set_tiling(c) - c.floating = false - c.maximized = false - c.maximized_vertical = false - c.maximized_horizontal = false - c.fullscreen = false + c.floating = false + c.maximized = false + c.maximized_vertical = false + c.maximized_horizontal = false + c.fullscreen = false end local function _area_tostring(wa) - return "{x:" .. tostring(wa.x) .. ",y:" .. tostring(wa.y) .. ",w:" .. tostring(wa.width) .. ",h:" .. tostring(wa.height) .. "}" + return "{x:" .. tostring(wa.x) .. ",y:" .. tostring(wa.y) .. ",w:" .. tostring(wa.width) .. ",h:" .. tostring(wa.height) .. "}" end local function shrink_area_with_gap(a, gap) @@ -67,500 +67,500 @@ local function shrink_area_with_gap(a, gap) end function module.restore_data(data) - if data.history_file then - local file, err = io.open(data.history_file, "r") - if err then - log(INFO, "cannot read history from " .. data.history_file) - else - data.cmds = {} - data.last_cmd = {} - local last_layout_name - for line in file:lines() do - if line:sub(1, 1) == "+" then - last_layout_name = line:sub(2, #line) - else - if last_layout_name ~= nil then - log(DEBUG, "restore last cmd " .. line .. " for " .. last_layout_name) - data.last_cmd[last_layout_name] = line - last_layout_name = nil - else - log(DEBUG, "restore cmd " .. line) - data.cmds[#data.cmds + 1] = line - end + if data.history_file then + local file, err = io.open(data.history_file, "r") + if err then + log(INFO, "cannot read history from " .. data.history_file) + else + data.cmds = {} + data.last_cmd = {} + local last_layout_name + for line in file:lines() do + if line:sub(1, 1) == "+" then + last_layout_name = line:sub(2, #line) + else + if last_layout_name ~= nil then + log(DEBUG, "restore last cmd " .. line .. " for " .. last_layout_name) + data.last_cmd[last_layout_name] = line + last_layout_name = nil + else + log(DEBUG, "restore cmd " .. line) + data.cmds[#data.cmds + 1] = line + end + end end - end - file:close() - end - end + file:close() + end + end - return data + return data end function module.create(data) - if data == nil then - data = module.restore_data({ - history_file = gfs.get_cache_dir() .. "/history_machi", - history_save_max = 100, - }) - end + if data == nil then + data = module.restore_data({ + history_file = gfs.get_cache_dir() .. "/history_machi", + history_save_max = 100, + }) + end - data.cmds = data.cmds or {} - data.last_cmd = data.last_cmd or {} - data.minimum_size = data.minimum_size or 100 + data.cmds = data.cmds or {} + data.last_cmd = data.last_cmd or {} + data.minimum_size = data.minimum_size or 100 - local function add_cmd(instance_name, cmd) - -- remove duplicated entries - local j = 1 - for i = 1, #data.cmds do - if data.cmds[i] ~= cmd then - data.cmds[j] = data.cmds[i] - j = j + 1 - end - end - for i = #data.cmds, j, -1 do - table.remove(data.cmds, i) - end - - data.cmds[#data.cmds + 1] = cmd - data.last_cmd[instance_name] = cmd - if data.history_file then - local file, err = io.open(data.history_file, "w") - if err then - log(ERROR, "cannot save history to " .. data.history_file) - else - for i = max(1, #data.cmds - data.history_save_max + 1), #data.cmds do - log(DEBUG, "save cmd " .. data.cmds[i]) - file:write(data.cmds[i] .. "\n") - end - for name, cmd in pairs(data.last_cmd) do - log(DEBUG, "save last cmd " .. cmd .. " for " .. name) - file:write("+" .. name .. "\n" .. cmd .. "\n") - end - end - file:close() - end - - return true - end - - - local function start_interactive(screen, embed_args) - local label_font_family = beautiful.get_font( - beautiful.font):get_family() - local label_size = dpi(30) - local info_size = dpi(60) - -- colors are in rgba - local border_color = with_alpha(api.gears.color( - api.beautiful.machi_editor_border_color or api.beautiful.border_focus), - api.beautiful.machi_editor_border_opacity or 0.75) - local active_color = with_alpha(api.gears.color( - api.beautiful.machi_editor_active_color or api.beautiful.bg_focus), - api.beautiful.machi_editor_active_opacity or 0.5) - local open_color = with_alpha(api.gears.color( - api.beautiful.machi_editor_open_color or api.beautiful.bg_normal), - api.beautiful.machi_editor_open_opacity or 0.5) - local closed_color = open_color - - if to_save == nil then - to_save = true - end - - screen = screen or awful.screen.focused() - local tag = screen.selected_tag - local gap = tag.gap or 0 - local layout = tag.layout - - if layout.machi_set_cmd == nil then - naughty.notify({ - text = "The layout to edit is not machi", - timeout = 3, - }) - return - end - - local cmd_index = #data.cmds + 1 - data.cmds[cmd_index] = "" - - local start_x = screen.workarea.x - local start_y = screen.workarea.y - - local kg - local infobox = wibox({ - screen = screen, - x = screen.workarea.x, - y = screen.workarea.y, - width = screen.workarea.width, - height = screen.workarea.height, - bg = "#ffffff00", - opacity = 1, - ontop = true, - type = "dock", - }) - infobox.visible = true - - workarea = embed_args and embed_args.workarea or screen.workarea - - local closed_areas - local open_areas - local pending_op - local current_cmd - local to_exit - local to_apply - - local key_translate_tab = { - ["Return"] = ".", - [" "] = "-", - } - - local function set_cmd(cmd) - local new_closed_areas, new_open_areas, new_pending_op = machi_engine.areas_from_command( - cmd, - { - x = workarea.x + gap, - y = workarea.y + gap, - width = workarea.width - gap * 2, - height = workarea.height - gap * 2 - }, - gap * 2 + data.minimum_size) - if new_closed_areas then - closed_areas, open_areas, pending_op = - new_closed_areas, new_open_areas, new_pending_op - current_cmd = cmd - - if embed_args then - current_info = - embed_args.cmd_prefix.."["..current_cmd.."]"..embed_args.cmd_suffix - else - current_info = cmd - end - - if #open_areas == 0 and not pending_op then - current_info = current_info .. " (enter to apply)" - end - return true - else - return false - end - end - - local function handle_key(key) - if key_translate_tab[key] ~= nil then - key = key_translate_tab[key] - end - - return set_cmd(current_cmd..key) - end - - - local function cleanup() - infobox.visible = false - end - - local function draw_info(context, cr, width, height) - cr:set_source_rgba(0, 0, 0, 0) - cr:rectangle(0, 0, width, height) - cr:fill() - - local msg, ext - - for i, a in ipairs(closed_areas) do - if not a.inhabitable then - local sa = shrink_area_with_gap(a, gap) - local to_highlight = false - if pending_op ~= nil then - to_highlight = a.group_id == op_count - end - cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) - cr:clip() - if to_highlight then - cr:set_source(active_color) - else - cr:set_source(closed_color) - end - cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) - cr:fill() - cr:set_source(border_color) - cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) - cr:set_line_width(10.0) - cr:stroke() - cr:reset_clip() + local function add_cmd(instance_name, cmd) + -- remove duplicated entries + local j = 1 + for i = 1, #data.cmds do + if data.cmds[i] ~= cmd then + data.cmds[j] = data.cmds[i] + j = j + 1 end - end + end + for i = #data.cmds, j, -1 do + table.remove(data.cmds, i) + end - for i, a in ipairs(open_areas) do - if not a.inhabitable then - local sa = shrink_area_with_gap(a, gap) - local to_highlight = false - if not pending_op then - to_highlight = i == #open_areas - else - to_highlight = a.group_id == op_count - end - cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) - cr:clip() - if i == #open_areas then - cr:set_source(active_color) - else - cr:set_source(open_color) - end - cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) - cr:fill() - - cr:set_source(border_color) - cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) - cr:set_line_width(10.0) - if to_highlight then - cr:stroke() - else - cr:set_dash({5, 5}, 0) - cr:stroke() - cr:set_dash({}, 0) - end - cr:reset_clip() + data.cmds[#data.cmds + 1] = cmd + data.last_cmd[instance_name] = cmd + if data.history_file then + local file, err = io.open(data.history_file, "w") + if err then + log(ERROR, "cannot save history to " .. data.history_file) + else + for i = max(1, #data.cmds - data.history_save_max + 1), #data.cmds do + log(DEBUG, "save cmd " .. data.cmds[i]) + file:write(data.cmds[i] .. "\n") + end + for name, cmd in pairs(data.last_cmd) do + log(DEBUG, "save last cmd " .. cmd .. " for " .. name) + file:write("+" .. name .. "\n" .. cmd .. "\n") + end end - end + file:close() + end - cr:select_font_face(label_font_family, "normal", "normal") - cr:set_font_size(info_size) - cr:set_font_face(cr:get_font_face()) - msg = current_info - ext = cr:text_extents(msg) - cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing) - cr:text_path(msg) - cr:set_source_rgba(1, 1, 1, 1) - cr:fill() - cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing) - cr:text_path(msg) - cr:set_source_rgba(0, 0, 0, 1) - cr:set_line_width(2.0) - cr:stroke() - end + return true + end - local function refresh() - log(DEBUG, "closed areas:") - for i, a in ipairs(closed_areas) do - log(DEBUG, " " .. _area_tostring(a)) - end - log(DEBUG, "open areas:") - for i, a in ipairs(open_areas) do - log(DEBUG, " " .. _area_tostring(a)) - end - infobox.bgimage = draw_info - end - local function get_final_cmd() - local final_cmd = current_cmd - if embed_args then - final_cmd = embed_args.cmd_prefix .. - machi_engine.areas_to_command(closed_areas, true) .. - embed_args.cmd_suffix - end - return final_cmd - end + local function start_interactive(screen, embed_args) + local label_font_family = beautiful.get_font( + beautiful.font):get_family() + local label_size = dpi(30) + local info_size = dpi(60) + -- colors are in rgba + local border_color = with_alpha( + gears.color(beautiful.machi_editor_border_color or beautiful.border_focus), + beautiful.machi_editor_border_opacity or 0.75) + local active_color = with_alpha( + gears.color(beautiful.machi_editor_active_color or beautiful.bg_focus), + beautiful.machi_editor_active_opacity or 0.5) + local open_color = with_alpha( + gears.color(beautiful.machi_editor_open_color or beautiful.bg_normal), + beautiful.machi_editor_open_opacity or 0.5) + local closed_color = open_color - log(DEBUG, "interactive layout editing starts") + if to_save == nil then + to_save = true + end - set_cmd("") - refresh() + screen = screen or awful.screen.focused() + local tag = screen.selected_tag + local gap = tag.gap or 0 + local layout = tag.layout - kg = awful.keygrabber.run( - function (mod, key, event) - if event == "release" then - return + if layout.machi_set_cmd == nil then + naughty.notify({ + text = "The layout to edit is not machi", + timeout = 3, + }) + return + end + + local cmd_index = #data.cmds + 1 + data.cmds[cmd_index] = "" + + local start_x = screen.workarea.x + local start_y = screen.workarea.y + + local kg + local infobox = wibox({ + screen = screen, + x = screen.workarea.x, + y = screen.workarea.y, + width = screen.workarea.width, + height = screen.workarea.height, + bg = "#ffffff00", + opacity = 1, + ontop = true, + type = "dock", + }) + infobox.visible = true + + workarea = embed_args and embed_args.workarea or screen.workarea + + local closed_areas + local open_areas + local pending_op + local current_cmd + local to_exit + local to_apply + + local key_translate_tab = { + ["Return"] = ".", + [" "] = "-", + } + + local function set_cmd(cmd) + local new_closed_areas, new_open_areas, new_pending_op = machi_engine.areas_from_command( + cmd, + { + x = workarea.x + gap, + y = workarea.y + gap, + width = workarea.width - gap * 2, + height = workarea.height - gap * 2 + }, + gap * 2 + data.minimum_size) + if new_closed_areas then + closed_areas, open_areas, pending_op = + new_closed_areas, new_open_areas, new_pending_op + current_cmd = cmd + + if embed_args then + current_info = + embed_args.cmd_prefix.."["..current_cmd.."]"..embed_args.cmd_suffix + else + current_info = cmd + end + + if #open_areas == 0 and not pending_op then + current_info = current_info .. " (enter to apply)" + end + return true + else + return false + end + end + + local function handle_key(key) + if key_translate_tab[key] ~= nil then + key = key_translate_tab[key] end - local ok, err = pcall( - function () - if key == "BackSpace" then - local alt = false - for _, m in ipairs(mod) do - if m == "Shift" then - alt = true - break - end - end - if alt then - if embed_args then - set_cmd(embed_args.original_cmd or "") - else - local areas = layout.machi_get_areas(screen, tag) - set_cmd(machi_engine.areas_to_command(areas)) - end - else - set_cmd(current_cmd:sub(1, #current_cmd - 1)) - end - elseif key == "Escape" then - table.remove(data.cmds, #data.cmds) - to_exit = true - elseif key == "Up" or key == "Down" then - if current_cmd ~= data.cmds[cmd_index] then - data.cmds[#data.cmds] = current_cmd - end + return set_cmd(current_cmd..key) + end - if key == "Up" and cmd_index > 1 then - cmd_index = cmd_index - 1 - elseif key == "Down" and cmd_index < #data.cmds then - cmd_index = cmd_index + 1 - end - log(DEBUG, "restore history #" .. tostring(cmd_index) .. ":" .. data.cmds[cmd_index]) - set_cmd(data.cmds[cmd_index]) - elseif #open_areas > 0 or pending_op then - handle_key(key) - else - if key == "Return" then - local alt = false - for _, m in ipairs(mod) do - if m == "Shift" then - alt = true - break + local function cleanup() + infobox.visible = false + end + + local function draw_info(context, cr, width, height) + cr:set_source_rgba(0, 0, 0, 0) + cr:rectangle(0, 0, width, height) + cr:fill() + + local msg, ext + + for i, a in ipairs(closed_areas) do + if not a.inhabitable then + local sa = shrink_area_with_gap(a, gap) + local to_highlight = false + if pending_op ~= nil then + to_highlight = a.group_id == op_count + end + cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) + cr:clip() + if to_highlight then + cr:set_source(active_color) + else + cr:set_source(closed_color) + end + cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) + cr:fill() + cr:set_source(border_color) + cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) + cr:set_line_width(10.0) + cr:stroke() + cr:reset_clip() + end + end + + for i, a in ipairs(open_areas) do + if not a.inhabitable then + local sa = shrink_area_with_gap(a, gap) + local to_highlight = false + if not pending_op then + to_highlight = i == #open_areas + else + to_highlight = a.group_id == op_count + end + cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) + cr:clip() + if i == #open_areas then + cr:set_source(active_color) + else + cr:set_source(open_color) + end + cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) + cr:fill() + + cr:set_source(border_color) + cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) + cr:set_line_width(10.0) + if to_highlight then + cr:stroke() + else + cr:set_dash({5, 5}, 0) + cr:stroke() + cr:set_dash({}, 0) + end + cr:reset_clip() + end + end + + cr:select_font_face(label_font_family, "normal", "normal") + cr:set_font_size(info_size) + cr:set_font_face(cr:get_font_face()) + msg = current_info + ext = cr:text_extents(msg) + cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing) + cr:text_path(msg) + cr:set_source_rgba(1, 1, 1, 1) + cr:fill() + cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing) + cr:text_path(msg) + cr:set_source_rgba(0, 0, 0, 1) + cr:set_line_width(2.0) + cr:stroke() + end + + local function refresh() + log(DEBUG, "closed areas:") + for i, a in ipairs(closed_areas) do + log(DEBUG, " " .. _area_tostring(a)) + end + log(DEBUG, "open areas:") + for i, a in ipairs(open_areas) do + log(DEBUG, " " .. _area_tostring(a)) + end + infobox.bgimage = draw_info + end + + local function get_final_cmd() + local final_cmd = current_cmd + if embed_args then + final_cmd = embed_args.cmd_prefix .. + machi_engine.areas_to_command(closed_areas, true) .. + embed_args.cmd_suffix + end + return final_cmd + end + + log(DEBUG, "interactive layout editing starts") + + set_cmd("") + refresh() + + kg = awful.keygrabber.run( + function (mod, key, event) + if event == "release" then + return + end + + local ok, err = pcall( + function () + if key == "BackSpace" then + local alt = false + for _, m in ipairs(mod) do + if m == "Shift" then + alt = true + break + end + end + if alt then + if embed_args then + set_cmd(embed_args.original_cmd or "") + else + local areas = layout.machi_get_areas(screen, tag) + set_cmd(machi_engine.areas_to_command(areas)) + end + else + set_cmd(current_cmd:sub(1, #current_cmd - 1)) + end + elseif key == "Escape" then + table.remove(data.cmds, #data.cmds) + to_exit = true + elseif key == "Up" or key == "Down" then + if current_cmd ~= data.cmds[cmd_index] then + data.cmds[#data.cmds] = current_cmd + end + + if key == "Up" and cmd_index > 1 then + cmd_index = cmd_index - 1 + elseif key == "Down" and cmd_index < #data.cmds then + cmd_index = cmd_index + 1 + end + + log(DEBUG, "restore history #" .. tostring(cmd_index) .. ":" .. data.cmds[cmd_index]) + set_cmd(data.cmds[cmd_index]) + elseif #open_areas > 0 or pending_op then + handle_key(key) + else + if key == "Return" then + local alt = false + for _, m in ipairs(mod) do + if m == "Shift" then + alt = true + break + end + end + + local instance_name, persistent = layout.machi_get_instance_info(tag) + if not alt and persistent then + table.remove(data.cmds, #data.cmds) + add_cmd(instance_name, get_final_cmd()) + current_info = "Saved!" + else + current_info = "Applied!" + end + + to_exit = true + to_apply = true end end - local instance_name, persistent = layout.machi_get_instance_info(tag) - if not alt and persistent then - table.remove(data.cmds, #data.cmds) - add_cmd(instance_name, get_final_cmd()) - current_info = "Saved!" - else - current_info = "Applied!" + refresh() + + if to_exit then + log(DEBUG, "interactive layout editing ends") + if to_apply then + layout.machi_set_cmd(get_final_cmd(), tag) + awful.layout.arrange(screen) + gears.timer{ + timeout = 1, + autostart = true, + singleshot = true, + callback = cleanup, + } + else + cleanup() + end end + end) - to_exit = true - to_apply = true - end - end + if not ok then + log(ERROR, "Getting error in keygrabber: " .. err) + to_exit = true + cleanup() + end - refresh() - - if to_exit then - log(DEBUG, "interactive layout editing ends") - if to_apply then - layout.machi_set_cmd(get_final_cmd(), tag) - awful.layout.arrange(screen) - gears.timer{ - timeout = 1, - autostart = true, - singleshot = true, - callback = cleanup, - } - else - cleanup() - end - end - end) - - if not ok then - log(ERROR, "Getting error in keygrabber: " .. err) - to_exit = true - cleanup() + if to_exit then + awful.keygrabber.stop(kg) + end end + ) + end - if to_exit then - awful.keygrabber.stop(kg) + local function run_cmd(cmd, screen, tag) + local gap = tag.gap + local areas, closed = machi_engine.areas_from_command( + cmd, + { + x = screen.workarea.x + gap, + y = screen.workarea.y + gap, + width = screen.workarea.width - gap * 2, + height = screen.workarea.height - gap * 2 + }, + gap * 2 + data.minimum_size) + if not areas or #closed > 0 then + return nil + end + for _, a in ipairs(areas) do + a.x = a.x + gap + a.y = a.y + gap + a.width = a.width - gap * 2 + a.height = a.height - gap * 2 + end + return areas + end + + local function get_last_cmd(name) + return data.last_cmd[name] + end + + function adjust_shares(c, axis, adj) + if not c:isvisible() or c.floating or c.immobilized or + not c.machi or not c.machi.area then + return + end + local screen = c.screen + local tag = screen.selected_tag + local layout = tag.layout + if not layout.machi_get_areas then return end + local areas = layout.machi_get_areas(screen, tag) + local key_shares = axis.."_shares" + local key_spare = axis.."_spare" + local key_parent_shares = "parent_"..axis.."_shares" + + if adj < 0 then + if axis == "x" and c.width + adj < data.minimum_size then + adj = data.minimum_size - c.width + elseif axis == "y" and c.height + adj < data.minimum_size then + adj = data.minimum_size - c.height end - end - ) - end + end - local function run_cmd(cmd, screen, tag) - local gap = tag.gap - local areas, closed = machi_engine.areas_from_command( - cmd, - { - x = screen.workarea.x + gap, - y = screen.workarea.y + gap, - width = screen.workarea.width - gap * 2, - height = screen.workarea.height - gap * 2 - }, - gap * 2 + data.minimum_size) - if not areas or #closed > 0 then - return nil - end - for _, a in ipairs(areas) do - a.x = a.x + gap - a.y = a.y + gap - a.width = a.width - gap * 2 - a.height = a.height - gap * 2 - end - return areas - end + local function adjust(parent_id, shares, adj) + -- The propagation part is questionable. But it is not critical anyway.. + if type(shares) ~= "table" then + local old = areas[parent_id].split[key_shares][shares][2] or 0 + areas[parent_id].split[key_shares][shares][2] = old + adj + else + local acc = 0 + for i = 1, #shares do + local old = areas[parent_id].split[key_shares][shares[i]][2] or 0 + local adj_split = i == #shares and adj - acc or math.floor(adj * i / #shares - acc + 0.5) + areas[parent_id].split[key_shares][shares[i]][2] = old + adj_split + acc = acc + adj_split + end + end + if adj <= 0 then + return #areas[parent_id].split[key_shares] > 1 + else + return areas[parent_id].split[key_spare] >= adj + end + end - local function get_last_cmd(name) - return data.last_cmd[name] - end + local area = c.machi.area + while areas[area].parent_id do + if adjust(areas[area].parent_id, areas[area][key_parent_shares], adj) then + break + end + area = areas[area].parent_id + end - function adjust_shares(c, axis, adj) - if not c:isvisible() or c.floating or c.immobilized or - not c.machi or not c.machi.area then - return - end - local screen = c.screen - local tag = screen.selected_tag - local layout = tag.layout - if not layout.machi_get_areas then return end - local areas = layout.machi_get_areas(screen, tag) - local key_shares = axis.."_shares" - local key_spare = axis.."_spare" - local key_parent_shares = "parent_"..axis.."_shares" + layout.machi_set_cmd(machi_engine.areas_to_command(areas), tag) + awful.layout.arrange(screen) + end - if adj < 0 then - if axis == "x" and c.width + adj < data.minimum_size then - adj = data.minimum_size - c.width - elseif axis == "y" and c.height + adj < data.minimum_size then - adj = data.minimum_size - c.height - end - end + function adjust_x_shares(c, adj) + adjust_shares(c, "x", adj) + end - local function adjust(parent_id, shares, adj) - -- The propagation part is questionable. But it is not critical anyway.. - if type(shares) ~= "table" then - local old = areas[parent_id].split[key_shares][shares][2] or 0 - areas[parent_id].split[key_shares][shares][2] = old + adj - else - local acc = 0 - for i = 1, #shares do - local old = areas[parent_id].split[key_shares][shares[i]][2] or 0 - local adj_split = i == #shares and adj - acc or math.floor(adj * i / #shares - acc + 0.5) - areas[parent_id].split[key_shares][shares[i]][2] = old + adj_split - acc = acc + adj_split - end - end - if adj <= 0 then - return #areas[parent_id].split[key_shares] > 1 - else - return areas[parent_id].split[key_spare] >= adj - end - end + function adjust_y_shares(c, adj) + adjust_shares(c, "y", adj) + end - local area = c.machi.area - while areas[area].parent_id do - if adjust(areas[area].parent_id, areas[area][key_parent_shares], adj) then - break - end - area = areas[area].parent_id - end - - layout.machi_set_cmd(machi_engine.areas_to_command(areas), tag) - awful.layout.arrange(screen) - end - - function adjust_x_shares(c, adj) - adjust_shares(c, "x", adj) - end - - function adjust_y_shares(c, adj) - adjust_shares(c, "y", adj) - end - - return { - start_interactive = start_interactive, - run_cmd = run_cmd, - get_last_cmd = get_last_cmd, - adjust_x_shares = adjust_x_shares, - adjust_y_shares = adjust_y_shares, - } + return { + start_interactive = start_interactive, + run_cmd = run_cmd, + get_last_cmd = get_last_cmd, + adjust_x_shares = adjust_x_shares, + adjust_y_shares = adjust_y_shares, + } end module.default_editor = module.create() diff --git a/init.lua b/init.lua index 341db39..30dad74 100644 --- a/init.lua +++ b/init.lua @@ -3,12 +3,12 @@ local layout = require(... .. ".layout") local editor = require(... .. ".editor") local switcher = require(... .. ".switcher") local function default_name(tag) - if tag.machi_name_cache == nil then - tag.machi_name_cache = - tostring(tag.screen.geometry.width) .. "x" .. tostring(tag.screen.geometry.height) .. "+" .. - tostring(tag.screen.geometry.x) .. "+" .. tostring(tag.screen.geometry.y) .. '+' .. tag.name - end - return tag.machi_name_cache + if tag.machi_name_cache == nil then + tag.machi_name_cache = + tostring(tag.screen.geometry.width) .. "x" .. tostring(tag.screen.geometry.height) .. "+" .. + tostring(tag.screen.geometry.x) .. "+" .. tostring(tag.screen.geometry.y) .. '+' .. tag.name + end + return tag.machi_name_cache end local default_editor = editor.default_editor local default_layout = layout.create{ name_func = default_name } @@ -18,25 +18,25 @@ local beautiful = require("beautiful") local icon_raw local source = debug.getinfo(1, "S").source if source:sub(1, 1) == "@" then - icon_raw = source:match("^@(.-)[^/]+$") .. "icon.png" + icon_raw = source:match("^@(.-)[^/]+$") .. "icon.png" end local function get_icon() - if icon_raw ~= nil then - return gcolor.recolor_image(icon_raw, beautiful.fg_normal) - else - return nil - end + if icon_raw ~= nil then + return gcolor.recolor_image(icon_raw, beautiful.fg_normal) + else + return nil + end end return { - engine = engine, - layout = layout, - editor = editor, - switcher = switcher, - default_name = default_name, - default_editor = default_editor, - default_layout = default_layout, - icon_raw = icon_raw, - get_icon = get_icon, + engine = engine, + layout = layout, + editor = editor, + switcher = switcher, + default_name = default_name, + default_editor = default_editor, + default_layout = default_layout, + icon_raw = icon_raw, + get_icon = get_icon, } diff --git a/layout.lua b/layout.lua index bcdaff3..983f59d 100644 --- a/layout.lua +++ b/layout.lua @@ -11,9 +11,9 @@ local INFO = 0 local DEBUG = -1 local module = { - log_level = WARNING, - global_default_cmd = "dw66.", - allow_shrinking_by_mouse_moving = false, + log_level = WARNING, + global_default_cmd = "dw66.", + allow_shrinking_by_mouse_moving = false, } local function log(level, msg) diff --git a/switcher.lua b/switcher.lua index 9b072b6..79e2caf 100644 --- a/switcher.lua +++ b/switcher.lua @@ -3,19 +3,16 @@ local machi = { engine = require((...):match("(.-)[^%.]+$") .. "engine"), } -local api = { - client = client, - beautiful = require("beautiful"), - wibox = require("wibox"), - awful = require("awful"), - screen = require("awful.screen"), - layout = require("awful.layout"), - naughty = require("naughty"), - gears = require("gears"), - lgi = require("lgi"), - dpi = require("beautiful.xresources").apply_dpi, +local capi = { + client = client } +local beautiful = require("beautiful") +local wibox = require("wibox") +local awful = require("awful") +local gears = require("gears") +local lgi = require("lgi") +local dpi = require("beautiful.xresources").apply_dpi local gtimer = require("gears.timer") local ERROR = 2 @@ -24,532 +21,532 @@ local INFO = 0 local DEBUG = -1 local module = { - log_level = WARNING, + log_level = WARNING, } local function log(level, msg) - if level > module.log_level then - print(msg) - end + if level > module.log_level then + print(msg) + end end local function min(a, b) - if a < b then return a else return b end + if a < b then return a else return b end end local function max(a, b) - if a < b then return b else return a end + if a < b then return b else return a end end local function with_alpha(col, alpha) - local r, g, b - _, r, g, b, _ = col:get_rgba() - return api.lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha) + local r, g, b + _, r, g, b, _ = col:get_rgba() + return lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha) end function module.start(c, exit_keys) - local tablist_font_desc = api.beautiful.get_merged_font( - api.beautiful.font, api.dpi(10)) - local font_color = with_alpha(api.gears.color(api.beautiful.fg_normal), 1) - local font_color_hl = with_alpha(api.gears.color(api.beautiful.fg_focus), 1) - local label_size = api.dpi(30) - local border_color = with_alpha(api.gears.color( - api.beautiful.machi_switcher_border_color or api.beautiful.border_focus), - api.beautiful.machi_switcher_border_opacity or 0.25) - local border_color_hl = with_alpha(api.gears.color( - api.beautiful.machi_switcher_border_hl_color or api.beautiful.border_focus), - api.beautiful.machi_switcher_border_hl_opacity or 0.75) - local fill_color = with_alpha(api.gears.color( - api.beautiful.machi_switcher_fill_color or api.beautiful.bg_normal), - api.beautiful.machi_switcher_fill_opacity or 0.25) - local box_bg = with_alpha(api.gears.color( - api.beautiful.machi_switcher_box_bg or api.beautiful.bg_normal), - api.beautiful.machi_switcher_box_opacity or 0.85) - local fill_color_hl = with_alpha(api.gears.color( - api.beautiful.machi_switcher_fill_color_hl or api.beautiful.bg_focus), - api.beautiful.machi_switcher_fill_hl_opacity or 1) - -- for comparing floats - local threshold = 0.1 - local traverse_radius = api.dpi(5) + local tablist_font_desc = beautiful.get_merged_font( + beautiful.font, dpi(10)) + local font_color = with_alpha(gears.color(beautiful.fg_normal), 1) + local font_color_hl = with_alpha(gears.color(beautiful.fg_focus), 1) + local label_size = dpi(30) + local border_color = with_alpha( + gears.color(beautiful.machi_switcher_border_color or beautiful.border_focus), + beautiful.machi_switcher_border_opacity or 0.25) + local border_color_hl = with_alpha( + gears.color(beautiful.machi_switcher_border_hl_color or beautiful.border_focus), + beautiful.machi_switcher_border_hl_opacity or 0.75) + local fill_color = with_alpha( + gears.color(beautiful.machi_switcher_fill_color or beautiful.bg_normal), + beautiful.machi_switcher_fill_opacity or 0.25) + local box_bg = with_alpha( + gears.color(beautiful.machi_switcher_box_bg or beautiful.bg_normal), + beautiful.machi_switcher_box_opacity or 0.85) + local fill_color_hl = with_alpha( + gears.color(beautiful.machi_switcher_fill_color_hl or beautiful.bg_focus), + beautiful.machi_switcher_fill_hl_opacity or 1) + -- for comparing floats + local threshold = 0.1 + local traverse_radius = dpi(5) - local screen = c and c.screen or api.screen.focused() - local tag = screen.selected_tag - local layout = tag.layout - local gap = tag.gap - local start_x = screen.workarea.x - local start_y = screen.workarea.y + local screen = c and c.screen or awful.screen.focused() + local tag = screen.selected_tag + local layout = tag.layout + local gap = tag.gap + local start_x = screen.workarea.x + local start_y = screen.workarea.y - if (c ~= nil and c.floating) or layout.machi_get_areas == nil then return end + if (c ~= nil and c.floating) or layout.machi_get_areas == nil then return end - local areas, draft_mode = layout.machi_get_areas(screen, screen.selected_tag) - if areas == nil or #areas == 0 then - return - end + local areas, draft_mode = layout.machi_get_areas(screen, screen.selected_tag) + if areas == nil or #areas == 0 then + return + end - local infobox = api.wibox({ - screen = screen, - x = screen.workarea.x, - y = screen.workarea.y, - width = screen.workarea.width, - height = screen.workarea.height, - bg = "#ffffff00", - opacity = 1, - ontop = true, - type = "dock", - }) - infobox.visible = true + local infobox = wibox({ + screen = screen, + x = screen.workarea.x, + y = screen.workarea.y, + width = screen.workarea.width, + height = screen.workarea.height, + bg = "#ffffff00", + opacity = 1, + ontop = true, + type = "dock", + }) + infobox.visible = true - local tablist = nil - local tablist_index = nil + local tablist = nil + local tablist_index = nil - local traverse_x, traverse_y - if c then - traverse_x = c.x + traverse_radius - traverse_y = c.y + traverse_radius - else - traverse_x = screen.workarea.x + screen.workarea.width / 2 - traverse_y = screen.workarea.y + screen.workarea.height / 2 - end + local traverse_x, traverse_y + if c then + traverse_x = c.x + traverse_radius + traverse_y = c.y + traverse_radius + else + traverse_x = screen.workarea.x + screen.workarea.width / 2 + traverse_y = screen.workarea.y + screen.workarea.height / 2 + end - local selected_area_ = nil - local function selected_area() - if selected_area_ == nil then - local min_dis = nil - for i, a in ipairs(areas) do - if not a.inhabitable then - local dis = - math.abs(a.x + traverse_radius - traverse_x) + math.abs(a.x + a.width - traverse_radius - traverse_x) - a.width + - math.abs(a.y + traverse_radius - traverse_y) + math.abs(a.y + a.height - traverse_radius - traverse_y) - a.height + - traverse_radius * 4 - if min_dis == nil or min_dis > dis then - min_dis = dis - selected_area_ = i - end - end - end - - if min_dis > 0 then - local a = areas[selected_area_] - local corners = { - {a.x + traverse_radius, a.y + traverse_radius}, - {a.x + traverse_radius, a.y + a.height - traverse_radius}, - {a.x + a.width - traverse_radius, a.y + traverse_radius}, - {a.x + a.width - traverse_radius, a.y + a.height - traverse_radius} - } - min_dis = nil - local min_i - for i, c in ipairs(corners) do - local dis = math.abs(c[1] - traverse_x) + math.abs(c[2] - traverse_y) - if min_dis == nil or min_dis > dis then - min_dis = dis - min_i = i - end - end - - traverse_x = corners[min_i][1] - traverse_y = corners[min_i][2] - end - end - return selected_area_ - end - - local function set_selected_area(a) - selected_area_ = a - end - - local function maintain_tablist() - if tablist == nil then - tablist = {} - - local active_area = selected_area() - for _, tc in ipairs(screen.tiled_clients) do - if not (tc.floating or tc.immobilized) - then - if areas[active_area].x <= tc.x + tc.width + tc.border_width * 2 and tc.x <= areas[active_area].x + areas[active_area].width and - areas[active_area].y <= tc.y + tc.height + tc.border_width * 2 and tc.y <= areas[active_area].y + areas[active_area].height - then - tablist[#tablist + 1] = tc - end - end - end - - tablist_index = 1 - - else - - local j = 0 - for i = 1, #tablist do - if tablist[i].valid then - j = j + 1 - tablist[j] = tablist[i] - elseif i <= tablist_index and tablist_index > 0 then - tablist_index = tablist_index - 1 - end - end - - for i = #tablist, j + 1, -1 do - table.remove(tablist, i) - end - end - - if c and not c.valid then c = nil end - if c == nil and #tablist > 0 then - c = tablist[tablist_index] - end - end - - local function draw_info(context, cr, width, height) - maintain_tablist() - - cr:set_source_rgba(0, 0, 0, 0) - cr:rectangle(0, 0, width, height) - cr:fill() - - local msg, ext - local active_area = selected_area() - for i, a in ipairs(areas) do - if not a.inhabitable or i == active_area then - cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) - cr:clip() - cr:set_source(fill_color) - cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) - cr:fill() - cr:set_source(i == active_area and border_color_hl or border_color) - cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) - cr:set_line_width(10.0) - cr:stroke() - cr:reset_clip() - end - end - - if #tablist > 0 then - local a = areas[active_area] - local pl = api.lgi.Pango.Layout.create(cr) - pl:set_font_description(tablist_font_desc) - - local vpadding = api.dpi(10) - local list_height = vpadding - local list_width = 2 * vpadding - local exts = {} - - for index, tc in ipairs(tablist) do - local label = tc.name or "" - pl:set_text(label) - local w, h - w, h = pl:get_size() - w = w / api.lgi.Pango.SCALE - h = h / api.lgi.Pango.SCALE - local ext = { width = w, height = h, x_bearing = 0, y_bearing = 0 } - exts[#exts + 1] = ext - list_height = list_height + ext.height + vpadding - list_width = max(list_width, w + 2 * vpadding) - end - - local x_offset = a.x + a.width / 2 - start_x - local y_offset = a.y + a.height / 2 - list_height / 2 + vpadding - start_y - - -- cr:rectangle(a.x - start_x, y_offset - vpadding - start_y, a.width, list_height) - -- cover the entire area - cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) - cr:set_source(fill_color) - cr:fill() - - cr:rectangle(a.x + (a.width - list_width) / 2 - start_x, a.y + (a.height - list_height) / 2 - start_y, list_width, list_height) - cr:set_source(box_bg) - cr:fill() - - for index, tc in ipairs(tablist) do - local label = tc.name or "" - local ext = exts[index] - if index == tablist_index then - cr:rectangle(x_offset - ext.width / 2 - vpadding / 2, y_offset - vpadding / 2, ext.width + vpadding, ext.height + vpadding) - cr:set_source(fill_color_hl) - cr:fill() - pl:set_text(label) - cr:move_to(x_offset - ext.width / 2 - ext.x_bearing, y_offset - ext.y_bearing) - cr:set_source(font_color_hl) - cr:show_layout(pl) - else - pl:set_text(label) - cr:move_to(x_offset - ext.width / 2 - ext.x_bearing, y_offset - ext.y_bearing) - cr:set_source(font_color) - cr:show_layout(pl) + local selected_area_ = nil + local function selected_area() + if selected_area_ == nil then + local min_dis = nil + for i, a in ipairs(areas) do + if not a.inhabitable then + local dis = + math.abs(a.x + traverse_radius - traverse_x) + math.abs(a.x + a.width - traverse_radius - traverse_x) - a.width + + math.abs(a.y + traverse_radius - traverse_y) + math.abs(a.y + a.height - traverse_radius - traverse_y) - a.height + + traverse_radius * 4 + if min_dis == nil or min_dis > dis then + min_dis = dis + selected_area_ = i + end + end end - y_offset = y_offset + ext.height + vpadding - end - end + if min_dis > 0 then + local a = areas[selected_area_] + local corners = { + {a.x + traverse_radius, a.y + traverse_radius}, + {a.x + traverse_radius, a.y + a.height - traverse_radius}, + {a.x + a.width - traverse_radius, a.y + traverse_radius}, + {a.x + a.width - traverse_radius, a.y + a.height - traverse_radius} + } + min_dis = nil + local min_i + for i, c in ipairs(corners) do + local dis = math.abs(c[1] - traverse_x) + math.abs(c[2] - traverse_y) + if min_dis == nil or min_dis > dis then + min_dis = dis + min_i = i + end + end - -- show the traverse point - cr:rectangle(traverse_x - start_x - traverse_radius, traverse_y - start_y - traverse_radius, traverse_radius * 2, traverse_radius * 2) - cr:set_source_rgba(1, 1, 1, 1) - cr:fill() - end + traverse_x = corners[min_i][1] + traverse_y = corners[min_i][2] + end + end + return selected_area_ + end - infobox.bgimage = draw_info + local function set_selected_area(a) + selected_area_ = a + end - local key_translate_tab = { - ["w"] = "Up", - ["a"] = "Left", - ["s"] = "Down", - ["d"] = "Right", - } + local function maintain_tablist() + if tablist == nil then + tablist = {} - api.awful.client.focus.history.disable_tracking() + local active_area = selected_area() + for _, tc in ipairs(screen.tiled_clients) do + if not (tc.floating or tc.immobilized) + then + if areas[active_area].x <= tc.x + tc.width + tc.border_width * 2 and tc.x <= areas[active_area].x + areas[active_area].width and + areas[active_area].y <= tc.y + tc.height + tc.border_width * 2 and tc.y <= areas[active_area].y + areas[active_area].height + then + tablist[#tablist + 1] = tc + end + end + end - local kg - local function exit() - api.awful.client.focus.history.enable_tracking() - if api.client.focus then - api.client.emit_signal("focus", api.client.focus) - end - infobox.visible = false - api.awful.keygrabber.stop(kg) - end + tablist_index = 1 - local function handle_key(mod, key, event) - if event == "release" then - if exit_keys and exit_keys[key] then - exit() - end - return - end - if key_translate_tab[key] ~= nil then - key = key_translate_tab[key] - end + else - maintain_tablist() - assert(tablist ~= nil) + local j = 0 + for i = 1, #tablist do + if tablist[i].valid then + j = j + 1 + tablist[j] = tablist[i] + elseif i <= tablist_index and tablist_index > 0 then + tablist_index = tablist_index - 1 + end + end - if key == "Tab" then - if #tablist > 0 then - tablist_index = tablist_index % #tablist + 1 + for i = #tablist, j + 1, -1 do + table.remove(tablist, i) + end + end + + if c and not c.valid then c = nil end + if c == nil and #tablist > 0 then c = tablist[tablist_index] - c:emit_signal("request::activate", "mouse.move", {raise=false}) - c:raise() + end + end - infobox.bgimage = draw_info - end - elseif key == "Up" or key == "Down" or key == "Left" or key == "Right" then - local shift = false - local ctrl = false - for i, m in ipairs(mod) do - if m == "Shift" then shift = true - elseif m == "Control" then ctrl = true + local function draw_info(context, cr, width, height) + maintain_tablist() + + cr:set_source_rgba(0, 0, 0, 0) + cr:rectangle(0, 0, width, height) + cr:fill() + + local msg, ext + local active_area = selected_area() + for i, a in ipairs(areas) do + if not a.inhabitable or i == active_area then + cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) + cr:clip() + cr:set_source(fill_color) + cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) + cr:fill() + cr:set_source(i == active_area and border_color_hl or border_color) + cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) + cr:set_line_width(10.0) + cr:stroke() + cr:reset_clip() end - end + end - local current_area = selected_area() + if #tablist > 0 then + local a = areas[active_area] + local pl = lgi.Pango.Layout.create(cr) + pl:set_font_description(tablist_font_desc) - if c and (shift or ctrl) then - if shift then - if current_area == nil or - areas[current_area].x ~= c.x or - areas[current_area].y ~= c.y - then - traverse_x = c.x + traverse_radius - traverse_y = c.y + traverse_radius - set_selected_area(nil) - end - elseif ctrl then - local ex = c.x + c.width + c.border_width * 2 - local ey = c.y + c.height + c.border_width * 2 - if current_area == nil or - areas[current_area].x + areas[current_area].width ~= ex or - areas[current_area].y + areas[current_area].height ~= ey - then - traverse_x = ex - traverse_radius - traverse_y = ey - traverse_radius - set_selected_area(nil) - end - end - end + local vpadding = dpi(10) + local list_height = vpadding + local list_width = 2 * vpadding + local exts = {} - local choice = nil - local choice_value - - current_area = selected_area() - - for i, a in ipairs(areas) do - if a.inhabitable then goto continue end - - local v - if key == "Up" then - if a.x < traverse_x + threshold - and traverse_x < a.x + a.width + threshold then - v = traverse_y - a.y - a.height - else - v = -1 - end - elseif key == "Down" then - if a.x < traverse_x + threshold - and traverse_x < a.x + a.width + threshold then - v = a.y - traverse_y - else - v = -1 - end - elseif key == "Left" then - if a.y < traverse_y + threshold - and traverse_y < a.y + a.height + threshold then - v = traverse_x - a.x - a.width - else - v = -1 - end - elseif key == "Right" then - if a.y < traverse_y + threshold - and traverse_y < a.y + a.height + threshold then - v = a.x - traverse_x - else - v = -1 - end + for index, tc in ipairs(tablist) do + local label = tc.name or "" + pl:set_text(label) + local w, h + w, h = pl:get_size() + w = w / lgi.Pango.SCALE + h = h / lgi.Pango.SCALE + local ext = { width = w, height = h, x_bearing = 0, y_bearing = 0 } + exts[#exts + 1] = ext + list_height = list_height + ext.height + vpadding + list_width = max(list_width, w + 2 * vpadding) end - if (v > threshold) and (choice_value == nil or choice_value > v) then - choice = i - choice_value = v - end - ::continue:: - end + local x_offset = a.x + a.width / 2 - start_x + local y_offset = a.y + a.height / 2 - list_height / 2 + vpadding - start_y - if choice == nil then - choice = current_area - if key == "Up" then - traverse_y = screen.workarea.y - elseif key == "Down" then - traverse_y = screen.workarea.y + screen.workarea.height - elseif key == "Left" then - traverse_x = screen.workarea.x - else - traverse_x = screen.workarea.x + screen.workarea.width - end - end + -- cr:rectangle(a.x - start_x, y_offset - vpadding - start_y, a.width, list_height) + -- cover the entire area + cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height) + cr:set_source(fill_color) + cr:fill() - if choice ~= nil then - traverse_x = max(areas[choice].x + traverse_radius, min(areas[choice].x + areas[choice].width - traverse_radius, traverse_x)) - traverse_y = max(areas[choice].y + traverse_radius, min(areas[choice].y + areas[choice].height - traverse_radius, traverse_y)) - tablist = nil - set_selected_area(nil) + cr:rectangle(a.x + (a.width - list_width) / 2 - start_x, a.y + (a.height - list_height) / 2 - start_y, list_width, list_height) + cr:set_source(box_bg) + cr:fill() - if c then - local in_draft = c and c.machi_draft - if in_draft == nil then in_draft = draft_mode end - if ctrl and in_draft then - local lu = c.machi.lu - local rd = c.machi.rd - - if shift then - lu = choice - if areas[rd].x + areas[rd].width <= areas[lu].x or - areas[rd].y + areas[rd].height <= areas[lu].y - then - rd = nil - end - else - rd = choice - if areas[rd].x + areas[rd].width <= areas[lu].x or - areas[rd].y + areas[rd].height <= areas[lu].y - then - lu = nil - end - end - - if lu ~= nil and rd ~= nil then - machi.layout.set_geometry(c, areas[lu], areas[rd], 0, c.border_width) - elseif lu ~= nil then - machi.layout.set_geometry(c, areas[lu], nil, 0, c.border_width) - elseif rd ~= nil then - c.x = min(c.x, areas[rd].x) - c.y = min(c.y, areas[rd].y) - machi.layout.set_geometry(c, nil, areas[rd], 0, c.border_width) - end - c.machi.lu = lu - c.machi.rd = rd - - c:emit_signal("request::activate", "mouse.move", {raise=false}) - c:raise() - api.layout.arrange(screen) - elseif shift then - -- move the window - if in_draft then - c.x = areas[choice].x - c.y = areas[choice].y - else - machi.layout.set_geometry(c, areas[choice], areas[choice], 0, c.border_width) - c.machi.area = choice - end - c:emit_signal("request::activate", "mouse.move", {raise=false}) - c:raise() - api.layout.arrange(screen) - - tablist = nil + for index, tc in ipairs(tablist) do + local label = tc.name or "" + local ext = exts[index] + if index == tablist_index then + cr:rectangle(x_offset - ext.width / 2 - vpadding / 2, y_offset - vpadding / 2, ext.width + vpadding, ext.height + vpadding) + cr:set_source(fill_color_hl) + cr:fill() + pl:set_text(label) + cr:move_to(x_offset - ext.width / 2 - ext.x_bearing, y_offset - ext.y_bearing) + cr:set_source(font_color_hl) + cr:show_layout(pl) + else + pl:set_text(label) + cr:move_to(x_offset - ext.width / 2 - ext.x_bearing, y_offset - ext.y_bearing) + cr:set_source(font_color) + cr:show_layout(pl) end - else - maintain_tablist() - -- move the focus - if #tablist > 0 and tablist[1] ~= c then - c = tablist[1] - api.client.focus = c + + y_offset = y_offset + ext.height + vpadding + end + end + + -- show the traverse point + cr:rectangle(traverse_x - start_x - traverse_radius, traverse_y - start_y - traverse_radius, traverse_radius * 2, traverse_radius * 2) + cr:set_source_rgba(1, 1, 1, 1) + cr:fill() + end + + infobox.bgimage = draw_info + + local key_translate_tab = { + ["w"] = "Up", + ["a"] = "Left", + ["s"] = "Down", + ["d"] = "Right", + } + + awful.client.focus.history.disable_tracking() + + local kg + local function exit() + awful.client.focus.history.enable_tracking() + if capi.client.focus then + capi.client.emit_signal("focus", capi.client.focus) + end + infobox.visible = false + awful.keygrabber.stop(kg) + end + + local function handle_key(mod, key, event) + if event == "release" then + if exit_keys and exit_keys[key] then + exit() + end + return + end + if key_translate_tab[key] ~= nil then + key = key_translate_tab[key] + end + + maintain_tablist() + assert(tablist ~= nil) + + if key == "Tab" then + if #tablist > 0 then + tablist_index = tablist_index % #tablist + 1 + c = tablist[tablist_index] + c:emit_signal("request::activate", "mouse.move", {raise=false}) + c:raise() + + infobox.bgimage = draw_info + end + elseif key == "Up" or key == "Down" or key == "Left" or key == "Right" then + local shift = false + local ctrl = false + for i, m in ipairs(mod) do + if m == "Shift" then shift = true + elseif m == "Control" then ctrl = true end end - infobox.bgimage = draw_info - end - elseif (key == "u" or key == "Prior") then - local current_area = selected_area() - if areas[current_area].parent_id then - tablist = nil - set_selected_area(areas[current_area].parent_id) - infobox.bgimage = draw_info - end - elseif key == "/" then - local current_area = selected_area() - local original_cmd = machi.engine.areas_to_command(areas, true, current_area) - areas[current_area].hole = true - local prefix, suffix = machi.engine.areas_to_command( - areas, false):match("(.*)|(.*)") - areas[current_area].hole = nil + local current_area = selected_area() - workarea = { - x = areas[current_area].x - gap * 2, - y = areas[current_area].y - gap * 2, - width = areas[current_area].width + gap * 4, - height = areas[current_area].height + gap * 4, - } - gtimer.delayed_call( - function () - print(layout.editor) - layout.editor.start_interactive( - screen, - { - workarea = workarea, - original_cmd = original_cmd, - cmd_prefix = prefix, - cmd_suffix = suffix, - } - ) - end - ) - exit() - elseif (key == "f" or key == ".") and c then - if c.machi_draft == nil then - c.machi_draft = not draft_mode - else - c.machi_draft = not c.machi_draft - end - api.layout.arrange(screen) - elseif key == "Escape" or key == "Return" then - exit() - else - log(DEBUG, "Unhandled key " .. key) - end - end + if c and (shift or ctrl) then + if shift then + if current_area == nil or + areas[current_area].x ~= c.x or + areas[current_area].y ~= c.y + then + traverse_x = c.x + traverse_radius + traverse_y = c.y + traverse_radius + set_selected_area(nil) + end + elseif ctrl then + local ex = c.x + c.width + c.border_width * 2 + local ey = c.y + c.height + c.border_width * 2 + if current_area == nil or + areas[current_area].x + areas[current_area].width ~= ex or + areas[current_area].y + areas[current_area].height ~= ey + then + traverse_x = ex - traverse_radius + traverse_y = ey - traverse_radius + set_selected_area(nil) + end + end + end - kg = api.awful.keygrabber.run( - function (...) - ok, _ = pcall(handle_key, ...) - if not ok then exit() end - end - ) + local choice = nil + local choice_value + + current_area = selected_area() + + for i, a in ipairs(areas) do + if a.inhabitable then goto continue end + + local v + if key == "Up" then + if a.x < traverse_x + threshold + and traverse_x < a.x + a.width + threshold then + v = traverse_y - a.y - a.height + else + v = -1 + end + elseif key == "Down" then + if a.x < traverse_x + threshold + and traverse_x < a.x + a.width + threshold then + v = a.y - traverse_y + else + v = -1 + end + elseif key == "Left" then + if a.y < traverse_y + threshold + and traverse_y < a.y + a.height + threshold then + v = traverse_x - a.x - a.width + else + v = -1 + end + elseif key == "Right" then + if a.y < traverse_y + threshold + and traverse_y < a.y + a.height + threshold then + v = a.x - traverse_x + else + v = -1 + end + end + + if (v > threshold) and (choice_value == nil or choice_value > v) then + choice = i + choice_value = v + end + ::continue:: + end + + if choice == nil then + choice = current_area + if key == "Up" then + traverse_y = screen.workarea.y + elseif key == "Down" then + traverse_y = screen.workarea.y + screen.workarea.height + elseif key == "Left" then + traverse_x = screen.workarea.x + else + traverse_x = screen.workarea.x + screen.workarea.width + end + end + + if choice ~= nil then + traverse_x = max(areas[choice].x + traverse_radius, min(areas[choice].x + areas[choice].width - traverse_radius, traverse_x)) + traverse_y = max(areas[choice].y + traverse_radius, min(areas[choice].y + areas[choice].height - traverse_radius, traverse_y)) + tablist = nil + set_selected_area(nil) + + if c then + local in_draft = c and c.machi_draft + if in_draft == nil then in_draft = draft_mode end + if ctrl and in_draft then + local lu = c.machi.lu + local rd = c.machi.rd + + if shift then + lu = choice + if areas[rd].x + areas[rd].width <= areas[lu].x or + areas[rd].y + areas[rd].height <= areas[lu].y + then + rd = nil + end + else + rd = choice + if areas[rd].x + areas[rd].width <= areas[lu].x or + areas[rd].y + areas[rd].height <= areas[lu].y + then + lu = nil + end + end + + if lu ~= nil and rd ~= nil then + machi.layout.set_geometry(c, areas[lu], areas[rd], 0, c.border_width) + elseif lu ~= nil then + machi.layout.set_geometry(c, areas[lu], nil, 0, c.border_width) + elseif rd ~= nil then + c.x = min(c.x, areas[rd].x) + c.y = min(c.y, areas[rd].y) + machi.layout.set_geometry(c, nil, areas[rd], 0, c.border_width) + end + c.machi.lu = lu + c.machi.rd = rd + + c:emit_signal("request::activate", "mouse.move", {raise=false}) + c:raise() + awful.layout.arrange(screen) + elseif shift then + -- move the window + if in_draft then + c.x = areas[choice].x + c.y = areas[choice].y + else + machi.layout.set_geometry(c, areas[choice], areas[choice], 0, c.border_width) + c.machi.area = choice + end + c:emit_signal("request::activate", "mouse.move", {raise=false}) + c:raise() + awful.layout.arrange(screen) + + tablist = nil + end + else + maintain_tablist() + -- move the focus + if #tablist > 0 and tablist[1] ~= c then + c = tablist[1] + capi.client.focus = c + end + end + + infobox.bgimage = draw_info + end + elseif (key == "u" or key == "Prior") then + local current_area = selected_area() + if areas[current_area].parent_id then + tablist = nil + set_selected_area(areas[current_area].parent_id) + infobox.bgimage = draw_info + end + elseif key == "/" then + local current_area = selected_area() + local original_cmd = machi.engine.areas_to_command(areas, true, current_area) + areas[current_area].hole = true + local prefix, suffix = machi.engine.areas_to_command( + areas, false):match("(.*)|(.*)") + areas[current_area].hole = nil + + workarea = { + x = areas[current_area].x - gap * 2, + y = areas[current_area].y - gap * 2, + width = areas[current_area].width + gap * 4, + height = areas[current_area].height + gap * 4, + } + gtimer.delayed_call( + function () + print(layout.editor) + layout.editor.start_interactive( + screen, + { + workarea = workarea, + original_cmd = original_cmd, + cmd_prefix = prefix, + cmd_suffix = suffix, + } + ) + end + ) + exit() + elseif (key == "f" or key == ".") and c then + if c.machi_draft == nil then + c.machi_draft = not draft_mode + else + c.machi_draft = not c.machi_draft + end + awful.layout.arrange(screen) + elseif key == "Escape" or key == "Return" then + exit() + else + log(DEBUG, "Unhandled key " .. key) + end + end + + kg = awful.keygrabber.run( + function (...) + ok, _ = pcall(handle_key, ...) + if not ok then exit() end + end + ) end return module