0

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

  • Are you creating your own wibox in which you draw your "scrolling things" (and nothing else)? If so, that would simplify things slightly. – Uli Schlachter May 05 '19 at 10:36

2 Answers2

1

Does the following example help you? It creates a new widget that draws some other widget with some hardcoded width/height and offset. A timer is used to animate things a bit.

-- 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()
local offset_x, offset_y = -20, 0
function own_widget:layout(context, width, height)
    -- No idea how to pick good widths and heights for the inner widget.
    return { wibox.widget.base.place_widget_at(screen[1].mytaglist, offset_x, offset_y, 200, 40) }
end
gears.timer.start_new(1, function()
    if offset_x < 0 then
        offset_x = 20
    else
        offset_x = -20
    end
    own_widget:emit_signal("widget::layout_changed")
    return true
end)
w:set_widget(own_widget)
Uli Schlachter
  • 9,337
  • 1
  • 23
  • 39
  • Thank you for answering my question! But in the meantime I got bored and decided to write my own layout that would act as a menu. I'm still working on it and the biggest hurdle is the fact that I have to use the more obscure, less documented parts of awesome, like implementing `:layout`, `:fit`. But yes, your answer does seem to be the core of what would solve my problem, however my situation is a bit more complicated. I have more widgets that I still want to draw, not just one widget in a wibox. I'll have to learn a bit more about implementing some "moving" behavior and then I'll be back. – LawsDontApplyToPigs May 06 '19 at 12:49
  • 1
    For your "more than one widget": Well... no, a wibox can only display a single widget. Most of the time, one uses a layout that then draws other widgets. You can also just use such a layout here. Put differently: Just use the same widget that you would use otherwise – Uli Schlachter May 06 '19 at 19:46
  • 1
    And for the other part of your question: When `:fit` is called, it basically means "here is how much space I can offer you, how much of it do you want?", so you return two numbers (width and height). And `:layout` is "I assigned this much space to you, please tell me where your child widgets are". It returns a table that can be filled like in my answer via `place_widget_at`. If the result of these functions changes (e.g. your widget becomes wider or you want to move one of your child widgets), you have to emit the `widget::layout_changed` signal, just as in my answer. – Uli Schlachter May 06 '19 at 19:47
  • Ohhhh that clears some confusion from the past few days! Thanks a Lot! – LawsDontApplyToPigs May 07 '19 at 05:14
  • Any hints on what your confusion was and how it could be avoided for others? Or did you just not find https://awesomewm.org/apidoc/documentation/04-new-widgets.md.html? – Uli Schlachter May 08 '19 at 16:16
  • I did find that, it's just that some things seemed very vague to me. For example, the way signals work is still pretty much black magic to me. The only things I know are the fact that you can do things like `mywidget:connect_signal("button::release", epic_function)` and that you can emit them from widgets to have certain methods be called again like `somewidget:emit_signal("widget::layout_changed")` will call the `:layout` method of `somewidget` (I think?? I'm not entirely sure even about that. I was planning on asking a question about signals) – LawsDontApplyToPigs May 08 '19 at 16:43
  • Also, there's a lot of mention of `context` and `cr` (this `cr` I think is the cairo context and the other is the place where the drawing will occur ((I think))). The same with these. I have no clue what these are. I tried looking for tutorials on cairo for these lua bindings that awesome uses (the lgi stuff). But it's been really hard to find anything. I just ended up looking at the official cairo documentation, trying to understand what some methods do. But even that was with variable success since most documentation is in C and I don't know C. – LawsDontApplyToPigs May 08 '19 at 16:49
  • And lastly, I think some examples would go a loong way. Just like I said, things seemed vague. Then I implemented things myself and then weird things happened like the fact that I implemented `:fit`, returning something dumb like `return 30, 30` and then nothing changed. Or the fact that I'd implement a `:layout` method and then took out the `:draw` method and then still things got drawn. So I was taken by surprise by the fact that even another method except `:draw` could draw (somehow). Little things like these where if I had some examples and illustrations, it'd be way easier to understand. – LawsDontApplyToPigs May 08 '19 at 17:01
  • I'm planning on submitting some patches to the docs to try to make it easier to understand the parts where I struggled, like writing examples. Also I found some bugs that I've fixed but I'm planning on submitting them once I'm done configuring my setup. But aside from this, thanks a lot for helping me understand all of this, it's been really helpful. – LawsDontApplyToPigs May 08 '19 at 17:07
1

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.)

Uli Schlachter
  • 9,337
  • 1
  • 23
  • 39
  • So I tried that with some random hastily made widget and it worked perfectly!! But then I tried to incorporate that into the scroll layout and everything broke. I put that input-handling code in the `:draw` method since that's the scope in which I could access the hierarchy that was already made in the code of the `scroll` layout. But then really weird bugs started happening like I'd click one widget and the callback would get called a huge amount of times. And the more I let the widget animate, the more times the callback would get called. Then I realized that happened because... – LawsDontApplyToPigs May 09 '19 at 19:39
  • ... of the fact that I put that input handling code in the `:draw` method, so that code would execute whenever the widget would redraw, which is 60 times a second in my case. So that's all well and good, but now I'll go off and try to find the right scope to put that code in, and then I'll be back to let you know how that went. But thanks so much for demonstrating how I can forward input events myself! – LawsDontApplyToPigs May 09 '19 at 19:42