diff --git a/widget/tag_preview.lua b/widget/tag_preview.lua index 2a181b4a..333243a6 100644 --- a/widget/tag_preview.lua +++ b/widget/tag_preview.lua @@ -1,87 +1,129 @@ --- --- Provides: --- bling::tag_preview::update -- first line is the signal --- t (tag) -- indented lines are function parameters --- bling::tag_preview::visibility --- s (screen) --- v (boolean) --- +local cairo = require("lgi").cairo local awful = require("awful") local wibox = require("wibox") local helpers = require(tostring(...):match(".*bling") .. ".helpers") -local gears = require("gears") +local gobject = require("gears.object") +local gtable = require("gears.table") +local gtimer = require("gears.timer") +local gmatrix = require("gears.matrix") +local gsurface = require("gears.surface") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi -local cairo = require("lgi").cairo +local collectgarbage = collectgarbage +local ipairs = ipairs +local pcall = pcall +local capi = {client = client, tag = tag} + +local tag_preview = {mt = {}} + +local function _get_widget_geometry(_hierarchy, widget) + local width, height = _hierarchy:get_size() + if _hierarchy:get_widget() == widget then + -- Get the extents of this widget in the device space + local x, y, w, h = gmatrix.transform_rectangle( + _hierarchy:get_matrix_to_device(), 0, 0, width, + height) + return {x = x, y = y, width = w, height = h, hierarchy = _hierarchy} + end + + for _, child in ipairs(_hierarchy:get_children()) do + local ret = _get_widget_geometry(child, widget) + if ret then return ret end + end +end + +local function get_widget_geometry(wibox, widget) + return _get_widget_geometry(wibox._drawable._widget_hierarchy, widget) +end + +function tag_preview:update(args2) + local args = self + local t = args2.t + local scale2 = args2.scale + + if not args.scale then args.scale = scale2 or 0.2 end + + if not args.coords and args.wibox and args.widget then + args.coords = get_widget_geometry(args.wibox, args.widget) + if args.offset.x ~= nil then + args.coords.x = args.coords.x + args.offset.x + end + if args.offset.y ~= nil then + args.coords.y = args.coords.y + args.offset.y + end + + self._private.widget.x = args.coords.x + self._private.widget.y = args.coords.y + end + + local geo = t.screen:get_bounding_geometry({ + honor_padding = args.padding, + honor_workarea = args.work_area + }) + + self._private.widget.maximum_width = + args.scale * geo.width + args.margin * 2 + self._private.widget.maximum_height = + args.scale * geo.height + args.margin * 2 -local function draw_widget( - t, - tag_preview_image, - scale, - screen_radius, - client_radius, - client_opacity, - client_bg, - client_border_color, - client_border_width, - widget_bg, - widget_border_color, - widget_border_width, - geo, - margin, - background_image -) local client_list = wibox.layout.manual() client_list.forced_height = geo.height - client_list.forced_width = geo.width - local tag_screen = t.screen - for i, c in ipairs(t:clients()) do - if not c.hidden and not c.minimized then - + client_list.forced_width = geo.widget - local img_box = wibox.widget ({ + for _, c in ipairs(t:clients()) do + if not c.hidden and not c.minimized then + local img_box = wibox.widget { resize = true, - forced_height = 100 * scale, - forced_width = 100 * scale, - widget = wibox.widget.imagebox, - }) + forced_height = 100 * args.scale, + forced_width = 100 * args.scale, + widget = wibox.widget.imagebox + } - -- If fails to set image, fallback to a awesome icon - if not pcall(function() img_box.image = gears.surface.load(c.icon) end) then - img_box.image = beautiful.theme_assets.awesome_icon (24, "#222222", "#fafafa") - end + -- If fails to set image, fallback to a awesome icon - if tag_preview_image then + if args.client_icon then + if not pcall(function() + img_box.image = gsurface.load(c.icon) + end) then + img_box.image = beautiful.theme_assets.awesome_icon(24, + "#222222", + "#fafafa") + end + end + + if args.tag_preview_image then if c.prev_content or t.selected then - local content + local content = nil if t.selected then - content = gears.surface(c.content) + content = gsurface(c.content) else - content = gears.surface(c.prev_content) + content = gsurface(c.prev_content) end local cr = cairo.Context(content) local x, y, w, h = cr:clip_extents() - local img = cairo.ImageSurface.create( - cairo.Format.ARGB32, - w - x, - h - y - ) + local img = cairo.ImageSurface.create(cairo.Format.ARGB32, + w - x, h - y) cr = cairo.Context(img) cr:set_source_surface(content, 0, 0) cr.operator = cairo.Operator.SOURCE cr:paint() img_box = wibox.widget({ - image = gears.surface.load(img), + image = gsurface.load(img), resize = true, - opacity = client_opacity, - forced_height = math.floor(c.height * scale), - forced_width = math.floor(c.width * scale), - widget = wibox.widget.imagebox, + opacity = args.client_opacity, + forced_height = c.height * args.scale, + forced_width = c.width * args.scale, + widget = wibox.widget.imagebox }) + end end + local c_bg = args.client_bg + + if c == capi.client.focus then c_bg = beautiful.xcolor4 end + local client_box = wibox.widget({ { nil, @@ -90,157 +132,143 @@ local function draw_widget( img_box, nil, expand = "outside", - layout = wibox.layout.align.horizontal, + layout = wibox.layout.align.horizontal }, nil, expand = "outside", - widget = wibox.layout.align.vertical, + widget = wibox.layout.align.vertical }, - forced_height = math.floor(c.height * scale), - forced_width = math.floor(c.width * scale), - bg = client_bg, - shape_border_color = client_border_color, - shape_border_width = client_border_width, - shape = helpers.shape.rrect(client_radius), - widget = wibox.container.background, + forced_height = math.floor(c.height * args.scale), + forced_width = math.floor(c.width * args.scale), + bg = c_bg, + shape_border_color = args.client_border_color, + shape_border_width = args.client_border_width, + shape = helpers.shape.rrect(args.client_border_radius), + widget = wibox.container.background }) client_box.point = { - x = math.floor((c.x - geo.x) * scale), - y = math.floor((c.y - geo.y) * scale), + x = math.floor((c.x - geo.x) * args.scale), + y = math.floor((c.y - geo.y) * args.scale) } client_list:add(client_box) + end end - return wibox.widget { + local w = wibox.widget { { - background_image, + args.background_image, { { - { - { - client_list, - forced_height = geo.height, - forced_width = geo.width, - widget = wibox.container.place, - }, - layout = wibox.layout.align.horizontal, - }, - layout = wibox.layout.align.vertical, + client_list, + forced_height = geo.height, + forced_width = geo.width, + valign = "center", + halign = "center", + widget = wibox.container.place }, - margins = margin, - widget = wibox.container.margin, + margins = args.tag_margin, + widget = wibox.container.margin }, layout = wibox.layout.stack }, - bg = widget_bg, - shape_border_width = widget_border_width, - shape_border_color = widget_border_color, - shape = helpers.shape.rrect(screen_radius), - widget = wibox.container.background, + bg = args.tag_bg, + shape_border_color = args.tag_border_color, + shape_border_width = args.tag_border_width, + shape = helpers.shape.rrect(args.tag_border_radius), + widget = wibox.container.background } + + self._private.widget.widget = w end -local enable = function(opts) - local opts = opts or {} - - local tag_preview_image = opts.show_client_content or false - local widget_x = opts.x or dpi(20) - local widget_y = opts.y or dpi(20) - local scale = opts.scale or 0.2 - local work_area = opts.honor_workarea or false - local padding = opts.honor_padding or false - local placement_fn = opts.placement_fn or nil - local background_image = opts.background_widget or nil - - local margin = beautiful.tag_preview_widget_margin or dpi(0) - local screen_radius = beautiful.tag_preview_widget_border_radius or dpi(0) - local client_radius = beautiful.tag_preview_client_border_radius or dpi(0) - local client_opacity = beautiful.tag_preview_client_opacity or 0.5 - local client_bg = beautiful.tag_preview_client_bg or "#000000" - local client_border_color = beautiful.tag_preview_client_border_color - or "#ffffff" - local client_border_width = beautiful.tag_preview_client_border_width - or dpi(3) - local widget_bg = beautiful.tag_preview_widget_bg or "#000000" - local widget_border_color = beautiful.tag_preview_widget_border_color - or "#ffffff" - local widget_border_width = beautiful.tag_preview_widget_border_width - or dpi(3) - - local tag_preview_box = awful.popup({ - type = "dropdown_menu", +local function new(args) + args = args or {} + + args.type = args.type or "dropdown_menu" + args.coords = args.coords or nil + args.placement = args.placement or nil + args.wibox = args.wibox + args.widget = args.widget + args.offset = args.offset or {} + args.padding = args.padding + args.work_area = args.work_area + args.scale = args.scale + args.margin = args.margin or dpi(0) + args.client_icon = args.client_icon + args.client_opacity = args.client_opacity or 0 + args.client_bg = args.client_bg or "#000000" + args.client_border_color = args.client_border_color or "#ffffff" + args.client_border_width = args.client_border_width or dpi(1) + args.client_border_radius = args.client_border_radius or dpi(0) + args.tag_margin = args.tag_margin or dpi(0) + args.tag_bg = args.tag_bg or "#000000" + args.tag_border_color = args.tag_border_color or "#ffffff" + args.tag_border_width = args.tag_border_width or dpi(0) + args.tag_border_radius = args.tag_border_radius or dpi(0) + args.background_image = args.background_image or nil + args.tag_preview_image = args.tag_preview_image + + local ret = gobject {} + ret._private = {} + + gtable.crush(ret, tag_preview) + gtable.crush(ret, args) + + ret._private.widget = awful.popup({ + type = ret.type, visible = false, ontop = true, - placement = placement_fn, - widget = wibox.container.background, - input_passthrough = true, + placement = ret.placement, + input_passthrough = ret.input_passthrough, bg = "#00000000", + widget = wibox.container.background -- A dummy widget to make awful.popup not scream }) - tag.connect_signal("property::selected", function(t) + capi.tag.connect_signal("property::selected", function(t) -- Awesome switches up tags on startup really fast it seems, probably depends on what rules you have set -- which can cause the c.content to not show the correct image - gears.timer - { + gtimer { timeout = 0.1, - call_now = false, + call_now = false, autostart = true, single_shot = true, callback = function() if t.selected == true then for _, c in ipairs(t:clients()) do - c.prev_content = gears.surface.duplicate_surface(c.content) + c.prev_content = gsurface.duplicate_surface(c.content) end end end } end) - awesome.connect_signal("bling::tag_preview::update", function(t) - local geo = t.screen:get_bounding_geometry({ - honor_padding = padding, - honor_workarea = work_area, - }) - - tag_preview_box.maximum_width = scale * geo.width + margin * 2 - tag_preview_box.maximum_height = scale * geo.height + margin * 2 - - - tag_preview_box.widget = draw_widget( - t, - tag_preview_image, - scale, - screen_radius, - client_radius, - client_opacity, - client_bg, - client_border_color, - client_border_width, - widget_bg, - widget_border_color, - widget_border_width, - geo, - margin, - background_image - ) - end) + return ret +end - awesome.connect_signal("bling::tag_preview::visibility", function(s, v) - if not placement_fn then - tag_preview_box.x = s.geometry.x + widget_x - tag_preview_box.y = s.geometry.y + widget_y - end +function tag_preview:get_widget() return self._private.widget.widget end - if v == false then - tag_preview_box.widget = nil - collectgarbage("collect") - end +function tag_preview:show(t) + self:update(t) + self._private.widget.visible = true +end - tag_preview_box.visible = v - end) +function tag_preview:hide() + self._private.widget.visible = false + self._private.widget.widget = nil + collectgarbage("collect") +end + +function tag_preview:toggle(t) + if self._private.widget.visible == true then + self:hide() + else + self:show(t) + end end -return {enable = enable, draw_widget = draw_widget} +function tag_preview.mt:__call(...) return new(...) end + +return setmetatable(tag_preview, tag_preview.mt) diff --git a/widget/task_preview.lua b/widget/task_preview.lua index 3b548673..566c988e 100644 --- a/widget/task_preview.lua +++ b/widget/task_preview.lua @@ -1,56 +1,70 @@ --- --- Provides: --- bling::task_preview::visibility --- s (screen) --- v (boolean) --- c (client) --- +local cairo = require("lgi").cairo local awful = require("awful") +local gobject = require("gears.object") +local gtable = require("gears.table") +local gtimer = require("gears.timer") +local gmatrix = require("gears.matrix") +local gsurface = require("gears.surface") +local naughty = require("naughty") local wibox = require("wibox") -local helpers = require(tostring(...):match(".*bling") .. ".helpers") -local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi -local cairo = require("lgi").cairo - --- TODO: rename structure to something better? -local function draw_widget( - c, - widget_template, - screen_radius, - widget_bg, - widget_border_color, - widget_border_width, - margin, - widget_width, - widget_height -) - if not pcall(function() - return type(c.content) - end) then - return +local collectgarbage = collectgarbage +local ipairs = ipairs +local pcall = pcall +local type = type +local capi = {tag = tag} + +local task_preview = {mt = {}} + +local function _get_widget_geometry(_hierarchy, widget) + local width, height = _hierarchy:get_size() + if _hierarchy:get_widget() == widget then + -- Get the extents of this widget in the device space + local x, y, w, h = gmatrix.transform_rectangle( + _hierarchy:get_matrix_to_device(), 0, 0, width, + height) + return {x = x, y = y, width = w, height = h, hierarchy = _hierarchy} end - local content = nil - if c.active then - content = gears.surface(c.content) - elseif c.prev_content then - content = gears.surface(c.prev_content) + for _, child in ipairs(_hierarchy:get_children()) do + local ret = _get_widget_geometry(child, widget) + if ret then return ret end end +end + +local function get_widget_geometry(wibox, widget) + return _get_widget_geometry(wibox._drawable._widget_hierarchy, widget) +end - local img = nil - if content ~= nil then - local cr = cairo.Context(content) - local x, y, w, h = cr:clip_extents() - img = cairo.ImageSurface.create(cairo.Format.ARGB32, w - x, h - y) - cr = cairo.Context(img) - cr:set_source_surface(content, 0, 0) - cr.operator = cairo.Operator.SOURCE - cr:paint() +function task_preview:show(c, args) + args = args or self.args or {} + + args.coords = args.coords or self.coords + args.wibox = args.wibox + args.widget = args.widget + args.offset = args.offset or {} + + if not args.coords and args.wibox and args.widget then + args.coords = get_widget_geometry(args.wibox, args.widget) + if args.offset.x ~= nil then + args.coords.x = args.coords.x + args.offset.x + end + if args.offset.y ~= nil then + args.coords.y = args.coords.y + args.offset.y + end + + self._private.widget.x = args.coords.x + self._private.widget.y = args.coords.y end - local widget = wibox.widget({ - (widget_template or { + local shoot = awful.screenshot {client = c} + shoot:refresh() + local ib = shoot.content_widget + ib.resize = true + + local widget = wibox.widget { + (self.widget_template or { { { { @@ -59,56 +73,51 @@ local function draw_widget( resize = true, forced_height = dpi(20), forced_width = dpi(20), - widget = wibox.widget.imagebox, + widget = wibox.widget.imagebox }, { { id = "name_role", align = "center", - widget = wibox.widget.textbox, + widget = wibox.widget.textbox }, left = dpi(4), right = dpi(4), - widget = wibox.container.margin, + widget = wibox.container.margin }, - layout = wibox.layout.align.horizontal, + layout = wibox.layout.align.horizontal }, { { - { - id = "image_role", - resize = true, - clip_shape = helpers.shape.rrect(screen_radius), - widget = wibox.widget.imagebox, - }, + id = "image_container_role", valign = "center", halign = "center", - widget = wibox.container.place, + widget = wibox.container.place }, - top = margin * 0.25, - widget = wibox.container.margin, + top = self.margin * 0.25, + widget = wibox.container.margin }, fill_space = true, - layout = wibox.layout.fixed.vertical, + layout = wibox.layout.fixed.vertical }, - margins = margin, - widget = wibox.container.margin, + margins = self.margin, + widget = wibox.container.margin }, - bg = widget_bg, - shape_border_width = widget_border_width, - shape_border_color = widget_border_color, - shape = helpers.shape.rrect(screen_radius), - widget = wibox.container.background, + bg = self.bg, + shape_border_width = self.border_width, + shape_border_color = self.border_color, + shape = self.shape, + widget = wibox.container.background }), - width = widget_width, - height = widget_height, - widget = wibox.container.constraint, - }) + width = self.forced_width, + height = self.forced_height, + widget = wibox.container.constraint + } -- TODO: have something like a create callback here? - for _, w in ipairs(widget:get_children_by_id("image_role")) do - w.image = img -- TODO: copy it with gears.surface.xxx or something + for _, w in ipairs(widget:get_children_by_id("image_container_role")) do + w.widget = ib -- TODO: copy it with gsurface.xxx or something end for _, w in ipairs(widget:get_children_by_id("name_role")) do @@ -119,81 +128,90 @@ local function draw_widget( w.image = c.icon -- TODO: detect clienticon end - return widget + self._private.widget.widget = widget + self._private.widget.visible = true +end + +function task_preview:hide() + self._private.widget.visible = false + self._private.widget.widget = nil + collectgarbage("collect") +end + +function task_preview:toggle(c, args) + if self._private.widget.visible == true then + self:hide() + else + self:show(c, args) + end end -local enable = function(opts) - local opts = opts or {} - - local widget_x = opts.x or dpi(20) - local widget_y = opts.y or dpi(20) - local widget_height = opts.height or dpi(200) - local widget_width = opts.width or dpi(200) - local placement_fn = opts.placement_fn or nil - - local margin = beautiful.task_preview_widget_margin or dpi(0) - local screen_radius = beautiful.task_preview_widget_border_radius or dpi(0) - local widget_bg = beautiful.task_preview_widget_bg or "#000000" - local widget_border_color = beautiful.task_preview_widget_border_color - or "#ffffff" - local widget_border_width = beautiful.task_preview_widget_border_width - or dpi(3) - - local task_preview_box = awful.popup({ - type = "dropdown_menu", +local function new(args) + args = args or {} + + args.type = args.type or "dropdown_menu" + args.coords = args.coords or nil + args.placement = args.placement or nil + args.forced_width = args.forced_width or dpi(200) + args.forced_height = args.forced_height or dpi(200) + args.input_passthrough = args.input_passthrough or false + + args.margin = args.margin or beautiful.task_preview_widget_margin or dpi(0) + args.shape = args.shape or beautiful.task_preview_widget_shape or nil + args.bg = args.bg or beautiful.task_preview_widget_bg or "#000000" + args.border_width = args.border_width or + beautiful.task_preview_widget_border_width or nil + args.border_color = args.border_color or + beautiful.task_preview_widget_border_color or + "#ffffff" + args.image_shape = args.image_shape or beautiful.task_preview_image_shape or + nil + + local ret = gobject {} + ret._private = {} + + gtable.crush(ret, task_preview) + gtable.crush(ret, args) + + ret._private.widget = awful.popup { + type = ret.type, visible = false, ontop = true, - placement = placement_fn, - widget = wibox.container.background, -- A dummy widget to make awful.popup not scream - input_passthrough = true, + placement = ret.placement, + input_passthrough = ret.input_passthrough, bg = "#00000000", - }) + widget = wibox.container.background -- A dummy widget to make awful.popup not scream + } - tag.connect_signal("property::selected", function(t) + capi.tag.connect_signal("property::selected", function(t) -- Awesome switches up tags on startup really fast it seems, probably depends on what rules you have set -- which can cause the c.content to not show the correct image - gears.timer - { + gtimer { timeout = 0.1, - call_now = false, + call_now = false, autostart = true, single_shot = true, callback = function() if t.selected == true then for _, c in ipairs(t:clients()) do - c.prev_content = gears.surface.duplicate_surface(c.content) + c.prev_content = gsurface.duplicate_surface(c.content) end end end } end) - awesome.connect_signal("bling::task_preview::visibility", function(s, v, c) - if v then - -- Update task preview contents - task_preview_box.widget = draw_widget( - c, - opts.structure, - screen_radius, - widget_bg, - widget_border_color, - widget_border_width, - margin, - widget_width, - widget_height - ) - else - task_preview_box.widget = nil - collectgarbage("collect") - end + return ret +end - if not placement_fn then - task_preview_box.x = s.geometry.x + widget_x - task_preview_box.y = s.geometry.y + widget_y - end +function task_preview.mt:__call(...) return new(...) end - task_preview_box.visible = v - end) -end +awesome.connect_signal("bling::task_preview::visibility", function(_, _, _) + naughty.notify { + title = "Bling Task Preview", + urgency = "normal", + message = "This method of activating the task preview has been deprecated. https://blingcorp.github.io/bling/#/widgets/task_preview" + } +end) -return { enable = enable, draw_widget = draw_widget } +return setmetatable(task_preview, task_preview.mt)