0

This is a follow-up to How to make mouse events propagate to widgets in `scroll` containers?

TL;DR:

The :layout method I implemented makes the widget draw all over the other widgets (see pictures below). How can I constrain where the :layout method will draw my widgets and where it will allow me to interact with the children?

Longer version:

So I ended up patching the scroll container and what I basically did was that I implemented a :layout method, based on the offset calculations that were already made in the original scroll container code.

This is basically what I did (I'll only put the relevant parts here):


-- this function emits signals `self._private.fps` times a second
local _need_scroll_redraw = function(self)
    if not self._private.paused and not self._private.scroll_timer then
        self._private.scroll_timer = timer.start_new(1 / self._private.fps, function()
            self._private.scroll_timer = nil
            self:emit_signal("widget::redraw_needed")
            self:emit_signal("widget::layout_changed") -- this is the only 
                                                       -- line that I added 
                                                       -- to this function
        end)
    end
end

local function calculate_info(self, context, width, height)
    -- this is a longer function, but in summary here we calculate the
    -- ideal size that the child would like to be, we see if the child
    -- is bigger than the space we have for drawing, and if it is, 
    -- we calculate offsets (and we call `_need_scroll_redraw` here) 
    -- so we later know where and how often to `:draw` and `:fit` it
end

function scroll:fit(context, width, height)
    local info = calculate_info(self, context, width, height)
    return info.fit_width, info.fit_height
end

function scroll:layout(context, width, height)
    local result = {}
    local i = calculate_info(self, context, width, height)

    table.insert(result, base.place_widget_at(
        self._private.widget, i.first_x, i.first_y, i.surface_width, i.surface_height 
    ))

    return result
end

-- Also, there was a `:draw` method, but I took it out entirely since
-- if I add the `:layout` method, things get drawn just fine
-- P.S: I also tried to implement what was in the `:draw` method, inside
-- the `:layout` method, so that it'll clip properly. I also tried that idea
-- with the `:before_draw_children` and `:after_draw_children` methods
-- but since I don't know how to use cairo, god knows what I wrote there,
-- but it didn't work

With the default scroll widget, my widget looks like this, but nothing I click on works: like this

But with the changes I made above, the widget with the rows does scroll, and I can click on each child just fine and have it react, it's just that it draws everything outside of its boundaries, and I can also click on stuff that's outside of the boundaries:

not like this

So my question is: how would I go about restricting what the :layout method displays, to have it work the way the default scroll layout works, but still be able to interact with the children?

1 Answers1

0

Untested, but I guess:

function scroll:before_draw_children(context, cr, width, height)
    cr:rectangle(0, 0, width, height)
    cr:clip()
end

This takes the rectangle describing the size of the scroll widget and clips all children to it, meaning that they cannot draw outside of the scroll widget.

Note that this is only prevents the drawing of the child widgets. It does not make awesomeWM "forget about them". What I mean with that is: You can still click on the where the widget would be if it were visible. The click will still be handled by the widget. Only the actual drawing of the child widget is prevented.

The above is why I asked in your original question whether you want just this one widget to be scrolled, without anything next to it. Because if the scroll widget covers the whole wibox, this whole class of issues just goes away.

And no, as far as I know / see, there is no way to prevent the not-drawn widgets from receiving button clicks.

Uli Schlachter
  • 9,337
  • 1
  • 23
  • 39
  • yes, that's exactly what I did last night in my horrible attempts at clipping the children AND the portion where you can interact with them. But just like you responded, I only clipped what was drawn, not also the portions where you can interact with the children if they were drawn at that offset. Is there really no other way? (i'll continue in the next comment since I'm running out of space) – LawsDontApplyToPigs May 08 '19 at 18:09
  • I remembered that some things (like the clients) had a `shape_input` attribute or something like that. The same was true for the `wibox`. I looked through the source code and I found both did something like this:`local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height); local cr = cairo.Context(img)` and then some other cairo magic and then for all I've tried, you can do things like set rounded corners on something, and then NOT be able to interact with that widget where the portion of the corner would've otherwise been drawn. Can't I do something like that in this case too? – LawsDontApplyToPigs May 08 '19 at 18:14
  • 1
    Well... the result for your `:layout` method is turned into a tree. This tree describes which widget has which others as children and what their sizes are. When some input event happens, this tree is used to figure out which widget was hit where. This tree is also used for drawing widgets. We can influence the drawing (as is done here), but input events directly use the tree (specifically: `wibox.drawable` in `button_signals` and `wibox.drawable.find_widgets` are used for input events). So, shapes could be used to "remove" part of the wibox completely, but not individual widgets. Sorry. – Uli Schlachter May 08 '19 at 18:56
  • 1
    Alternatively, you could do something similar to what the scroll widget in awesome currently does: Draw the children without using `:layout`, so that the rest of awesome does not know about them and cannot forward input events. Next (and this is something that the current code does not do), we could forward input events ourselves to child widgets without using the already existing code in AwesomeWM. However, this is not really possible with the current scroll widget (for $REASONS) and thus complicated.... – Uli Schlachter May 08 '19 at 18:58
  • ahh I see so you'd just draw the widgets with the `:draw` method instead of the `:layout` method and then you'd treat event handling separately? Is that correct? If so, would that actually allow me to accomplish what I'm looking for? and if that's true, can you roughly direct me into what I'd have to do and learn to make this work? I'd be very appreciative even of hints, as I don't really know what to do next to make this work. – LawsDontApplyToPigs May 08 '19 at 19:13
  • That better fits to your other (original) question, so I'll answer there. – Uli Schlachter May 09 '19 at 04:58