Repository: qtile/qtile

Issue:
`Sep` widget inconsistent rendering on `0.20` release
### Description
The `Sep` widget fails to render correctly when used via an assigned variable.
eg:
```python
basic_sep = Sep(foreground="#1e222a", linewidth=4)
line_sep = Sep(foreground="#abb2bf", linewidth=1, padding=10)
```
I've been experiencing this since the `0.20` release, and rolling back to `0.19` resolves this issue.

### Steps to reproduce
1. Be on `0.20` release of qtile
2. Use [this](https://gist.github.com/jamestrew/06dbc832a259edf55ca5121fbf5e9f74#file-config-py) as part of your Qtile config.py (replacing your `widget_defaults` and `screens`)
3. Restart Qtile

### Expected Behavior
Click into and zoom in for better look.
![image](https://user-images.githubusercontent.com/66286082/151672628-687997e1-7088-4006-821c-539bfca25813.png)
The `Sep` widget renders with the correct `foreground`, `linewidth` and `padding`.

### Actual Behavior
Click into and zoom in for better look.
![image](https://user-images.githubusercontent.com/66286082/151672765-2bd32b10-f166-430f-8e74-736f518c976b.png)
Notice on the left hand side, the `Sep` widgets are rendering transparently. Moving to the right, they seem to fail to render at all, causing all sorts of clashing between the other widgets. The two `Sep` widgets (`line_sep` left of the `Clock` and `basic_sep` after the `Systray`)  finally renders as indented.

Replacing each of the `line_sep` and `basic_sep` with `Sep(foreground="#abb2bf", linewidth=1, padding=10)` and `Sep(foreground="#1e222a", linewidth=4)` respectively resolves this issue. [Try here](https://gist.github.com/jamestrew/092c78fb4d10c757b7839f05dc2e916e).

Interestingly, I also tried both `0.19` and `0.20` with & without a second monitor and on my second monitor, the `Sep` widgets render correctly in all scenarios.
I suspect this issue has something to do with the render orders.

### System Info
System: `Linux 5.16.3-arch1-1`


@elParaguayo I noticed many of your PRs on the widgets where incorporated into the latest release, maybe you have an idea of what may be happening here?


Discussion:
When you use a named instance multiple times, it becomes `Mirror` object so something's obviously gone wrong in the logic there but I haven't touched that class for quite a while.

Are you able to do a `git bisect` to isolate the offending commit?
Here it is
```
d7363d9b763add64ef515484ebd09d370761c950 is the first bad commit
commit d7363d9b763add64ef515484ebd09d370761c950
Author: elParaguayo <elparaguayocode@gmail.com>
Date:   Sun Jan 16 09:17:44 2022 +0000

    Fix reconfiguration of bars on screen_change event

    Previously, once the bar was configured, any future configurations
    were blocked. This was a consequence of trying to stop margins being
    added multiple times.

    This PR addresses this by resetting the `configured` flag when a
    screen_change event arises.

    Fixes #3039
    Fixes #3148
    Fixes #3150

 CHANGELOG                |  1 +
 libqtile/bar.py          | 14 ++++++++++----
 libqtile/config.py       |  5 ++++-
 libqtile/core/manager.py |  4 ++--
 4 files changed, 17 insertions(+), 7 deletions(-)
```
Cool. Thanks. Always nice to see my own name on the offending commit 😉 

Let me look at fixing this.
:grin: 
Thank you. Appreciate it.
I'm pretty sure I know what I did wrong too...

Resolving patch:
diff --git a/libqtile/bar.py b/libqtile/bar.py
--- a/libqtile/bar.py
+++ b/libqtile/bar.py
@@ -294,10 +294,10 @@ def _configure(self, qtile, screen):
                 self._configure_widget(i)
         else:
             for idx, i in enumerate(self.widgets):
-                # Create a mirror if this widget is being placed on a different bar
+                # Create a mirror if this widget is already configured but isn't a Mirror
                 # We don't do isinstance(i, Mirror) because importing Mirror (at the top)
                 # would give a circular import as libqtile.widget.base imports lbqtile.bar
-                if i.configured and i.bar != self and i.__class__.__name__ != "Mirror":
+                if i.configured and i.__class__.__name__ != "Mirror":
                     i = i.create_mirror()
                     self.widgets[idx] = i
                 success = self._configure_widget(i)


Test patch:
diff --git a/test/widgets/test_base.py b/test/widgets/test_base.py
--- a/test/widgets/test_base.py
+++ b/test/widgets/test_base.py
@@ -18,6 +18,7 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 import libqtile.config
+from libqtile.widget import TextBox
 from libqtile.widget.base import _Widget
 
 
@@ -68,3 +69,41 @@ def test_multiple_timers(minimal_conf_noscreen, manager_nospawn):
     # Verify that `finalize()` cancels all timers.
     manager_nospawn.c.widget["timerwidget"].eval("self.finalize()")
     assert manager_nospawn.c.widget["timerwidget"].get_active_timers() == 0
+
+
+def test_mirrors_same_bar(minimal_conf_noscreen, manager_nospawn):
+    """Verify that mirror created when widget reused in same bar."""
+    config = minimal_conf_noscreen
+    tbox = TextBox("Testing Mirrors")
+    config.screens = [libqtile.config.Screen(top=libqtile.bar.Bar([tbox, tbox], 10))]
+
+    manager_nospawn.start(config)
+    info = manager_nospawn.c.bar["top"].info()["widgets"]
+
+    # First instance is retained, second is replaced by mirror
+    assert len(info) == 2
+    assert [w["name"] for w in info] == ["textbox", "mirror"]
+
+
+def test_mirrors_different_bar(minimal_conf_noscreen, manager_nospawn):
+    """Verify that mirror created when widget reused in different bar."""
+    config = minimal_conf_noscreen
+    tbox = TextBox("Testing Mirrors")
+    config.fake_screens = [
+        libqtile.config.Screen(top=libqtile.bar.Bar([tbox], 10), x=0, y=0, width=400, height=600),
+        libqtile.config.Screen(
+            top=libqtile.bar.Bar([tbox], 10), x=400, y=0, width=400, height=600
+        ),
+    ]
+
+    manager_nospawn.start(config)
+    screen0 = manager_nospawn.c.screen[0].bar["top"].info()["widgets"]
+    screen1 = manager_nospawn.c.screen[1].bar["top"].info()["widgets"]
+
+    # Original widget should be in the first screen
+    assert len(screen0) == 1
+    assert [w["name"] for w in screen0] == ["textbox"]
+
+    # Widget is replaced with a mirror on the second screen
+    assert len(screen1) == 1
+    assert [w["name"] for w in screen1] == ["mirror"]