Search code examples
awesome-wm

How to make mouse events propagate to widgets in `scroll` containers?


So I know that on the official documentation it says

Please note that mouse events do not propagate to widgets inside of the scroll container.

But what I'm trying to do is exactly that: How can I make mouse events propagate through this widget? Is there a way?

What I'm trying to do is make a "to-do app" built into awesome. But for that I actually need to have items (which will be the tasks I want to get done)

So I want to have a menu with widgets inside it that are rows, so that I can use it to scroll with the mouse wheel (or the keyboard) up and down, but still be able to select stuff. I think I can do the scrolling with the mouse through setting the widget's buttons and tweaking with the scroll container's :pause, :continue, etc. methods but I can't do the clicking part.

Therefore, my question is: how can I accomplish this? even if it's not through using the scroll container, how can I make this work?

To get an idea of what I want, here's what I made so far:

uhhhhh menu


Solution

  • Okay, so you want the complicated version that will clip its contained widget to its size and also handle input events. The following is the complicated and slow version:

    local inner_widget = screen[1].mytaglist
    local inner_width, inner_height = 200, 40
    -- No idea how to pick a good width and height for the wibox.
    local w = wibox{ x = 100, y = 100, width = 100, height = 20, visible = true }
    local own_widget = wibox.widget.base.make_widget()
    w:set_widget(own_widget)
    local offset_x, offset_y = -20, 0
    local own_context = { screen = screen[1], dpi = 92 } -- We have to invent something here... :-(
    local hierarchy
    hierarchy = wibox.hierarchy.new(own_context, inner_widget, inner_width, inner_height, function()
        own_widget:emit_signal("widget::redraw_needed")
    end, function()
        hierarchy:update(own_context, inner_widget, inner_width, inner_height)
        own_widget:emit_signal("widget::redraw_needed")
    end, nil)
    function own_widget:draw(context, cr, width, height)
        -- This does the scrolling
        cr:translate(offset_x, offset_y)
    
        -- Then just draw the inner stuff directly
        hierarchy:draw(own_context, cr)
    end
    -- Start a timer to simulate scrolling: Once per second we move things slightly
    gears.timer.start_new(1, function()
        offset_x = - offset_x
        own_widget:emit_signal("widget::redraw_needed")
        return true
    end)
    -- Finally, make input events work
    local function button_signal(name)
        -- This function is basically copy&paste from find_widgets() in
        -- wibox.drawable
        local function traverse_hierarchy_tree(h, x, y, ...)
            local m = h:get_matrix_from_device()
    
            -- Is (x,y) inside of this hierarchy or any child (aka the draw extents)?
            -- If not, we can stop searching.
            local x1, y1 = m:transform_point(x, y)
            local x2, y2, w2, h2 = h:get_draw_extents()
            if x1 < x2 or x1 >= x2 + w2 then
                return
            end
            if y1 < y2 or y1 >= y2 + h2 then
                return
            end
            -- Is (x,y) inside of this widget?
            -- If yes, we have to emit the signal on the widget.
            local width, height = h:get_size()
            if x1 >= 0 and y1 >= 0 and x1 <= width and y1 <= height then
                h:get_widget():emit_signal(name, x1, y1, ...)
            end
            -- Continue searching in all children.
            for _, child in ipairs(h:get_children()) do
                traverse_hierarchy_tree(child, x, y, ...)
            end
        end
        own_widget:connect_signal(name, function(_, x, y, ...)
            -- Translate to "local" coordinates
            x = x - offset_x
            y = y - offset_y
            -- Figure out which widgets were hit and emit the signal on them
            traverse_hierarchy_tree(hierarchy, x, y, ...)
        end)
    end
    button_signal("button::press")
    button_signal("button::release")
    

    Instead of letting AwesomeWM handle everything and just placing another widget in :layout, this code does more itself. Namely, it directly manages a widget tree (called a "hierarchy" in AwesomeWM) and draws it itself. Half of this code is then responsible for handling button events: When one comes in, this code forwards it to the right widget, taking the current scrolling into account.

    Note that this redraws everything that is shown by this custom widget whenever anything changes. I guess for your scrolling problem this is necessary anyway, because "everything changes" when you scroll a bit. However, when AwesomeWM draws some widgets itself, it tries to only redraw the part that actually changed. For example, if the clock updates because the time changed, only the clock will be redrawn. This code here instead redraws everything always.

    (And yes, I know that this code is quite spaghetti-y and bad, but it should help you figure out the necessary ingredients.)