Open sonofevil opened 2 years ago
Well, I tested that with Firefox (which required some heavy-ish changes) and maybe it doesn't work like it does on Waterfox. At least, I don't see how the behavior is any better than what the multi_row_tabs.css has. The only difference I can tell is that pinned tabs are on separate row - that is by itself kinda neat and maybe someone would prefer that (and can be done with pretty simple css addition), but I don't see how it makes tab dragging any better.
Which part exactly didn't work, if you don't mind me asking? Did it fail to keep both kinds of tabs on the same line when they didn't exceed the width together? Or something else?
It makes tab dragging better insofar as it delays the point at which it breaks. The more pinned tabs you have, the more significant will the difference be.
Here is an example of the behavior: https://i.imgur.com/NqDlisY.png https://i.imgur.com/UFNb7RK.png
Note how after adding the tab and triggering the line break, you'd still be able to drag all tabs, despite the line break, which you wouldn't be if some unpinned tabs stayed on the first row.
I've been thinking of building on this by making the whole tab bar horizontally scrollable, and prevent it from ever going beyond two rows. That way dragging would always work, at least in theory. Easier said than done tho.
Sorry, it's been a while but I've now managed to give this a bit more thought.
It didn't appear to have any noticeable effect because I didn't try it with enough pinned tabs for it to really matter.
Also a thing I didn't understand previously was that you intended to make the tabs show on one line if they seem to fit. That would be nice to have indeed, but I really don't want to do that IF it requires us to set width for tabs - and it looks to me to be a necessity.
For now, I have made a patch file to multi-row_tabs.css that just separates pinned tabs to separate row(s). If we can figure out a clean way to make non-pinned tabs show on the same row should they fit then it would be nice to add that.
Very nice. I've also been working on improving behavior, but haven't updated the repo in a while. I agree that fixed width is suboptimal. Maybe there is some way to make them shrink automatically.
As an experiment I tried limiting the rows to 2 by making the tab bar sideway-scrollable and 4 times its normal width. That way dragging would be intact for a much longer time. But of course it limits the utility of having multiple rows in the first place. Also I can't figure out how to restore scroll buttons when there are multiple rows. Mousewheel scrolling is only possible when holding shift, which is too inconvenient for me, and when I try to drag the scrollbar, I drag the whole window instead (might be Linux-specific).
Another idea I had would be to only activate the outlined behavior via some pseudo class like hover. Then the behavior would only activate when needed, giving us the best of both worlds. Simple hover isn't ideal of course since it would make clicking on tabs a pain.
Alright, here's a kinda hacky workaround for most dragging situations:
#tabbrowser-tabs[movingtab="true"] arrowscrollbox {
text-align: center !important;
}
#tabbrowser-tabs[movingtab="true"] .tabbrowser-tab {
width: 2% !important;
min-width: 2% !important;
}
#tabbrowser-tabs[movingtab="true"] .tabbrowser-tab .tab-close-button {
display: none;
}
#tabbrowser-tabs[movingtab="true"] .tabbrowser-tab .tab-content {
padding: unset !important;
}
This simply shrinks all tabs down to 2% of the box width when dragging starts, meaning dragging will work with up to 50 tabs, or up to 50 pinned and 50 unpinned tabs if you use my previous line-break hack. Disadvantages:
Edit: Hmm no, this somehow breaks dragging. Half of the time the UI fails to recognize that the tab was dropped, and the styling get stuck.
There is potential in the movingtab=true selector, I'm trying to make it so that when a tab is dragged, the layout resets to Firefox default. Only problem is that the scrollbox element is inside a shadow root, and the movingtab=true property is outside of it. I have no idea how to circumvent this.
I think I tested doing that sort of "revert to normal" while dragging tabs at some point. It just wasn't all that good because the dragged tab might end up anywhere in the tab strip. But I would certainly be interested if you can make it work somehow.
I may have found a different solution:
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(5) {
margin-right: calc(1 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(5) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-1) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(6) {
margin-right: calc(2 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(6) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-2) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(7) {
margin-right: calc(3 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(7) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-3) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(8) {
margin-right: calc(4 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(8) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-4) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(9) {
margin-right: calc(5 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(9) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-5) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(10) {
margin-right: calc(6 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(10) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-6) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(11) {
margin-right: calc(7 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(11) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-7) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(12) {
margin-right: calc(8 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(12) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-8) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(13) {
margin-right: calc(9 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(13) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-9) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(14) {
margin-right: calc(10 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(14) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-10) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(15) {
margin-right: calc(11 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(15) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-11) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(16) {
margin-right: calc(12 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(16) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-12) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(17) {
margin-right: calc(13 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(17) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-13) * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(18) {
margin-right: calc(14 * var(--multirow-tab-min-width)) !important;
}
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-child(18) + .tabbrowser-tab:not([pinned],[first-visible-unpinned-tab]) {
margin-left: calc((-14) * var(--multirow-tab-min-width)) !important;
}
Of course this is ugly and clunky. But it works. The unpinned tabs all break to the next line together when necessary. This is achieved by making the right margin of the first unpinned tab the same as the minimum length of all the tabs next to it, and making the other tabs "ignore" that margin by inverting it for the first one. That way the flexbox treats all unpinned tabs as if they were one wide tab. Unlike my earlier suggestion, this solution is agnostic wrt exactly how many tabs can actually fit inside the width of a row at any given time, and hence doesn't have to force their width (which solves the concern of @MrOtherGuy). But it needs to iterate through all possible numbers of unpinned tabs that could fit inside the width of a row. the above example is capped at a maximum number of 15, which is the case on a 1920px wide screen when min tab width is 100px. don't ask me how that math makes sense.
Now if anyone has an idea how to optimize this, that would be great. I tried nesting, but that doesn't seem to work in the Firefox GUI css.
That sure looks ugly, though you could make it bit tidier by removing all those :not([pinned],[first-visible-unpinned-tab])
parts, since the next tab after one that has [first-visible-unpinned-tab] can't possibly be pinned or first-visible-unpinned-tab itself.
However, I fail to see how that changes anything. If I add that to just multi-row_tabs.css then the behavior is pretty much exactly the same as normally. But if I also include that _separate_pinned_tabs_row.css file then this doesn't seem to have any effect at all and pinned tabs still have their completely separate row.
Yeah it's pointless together with the _separate_pinned_tabs_row.css
because it's an alternative to it. A good way to visualize what this does is to imagine a _separate_pinned_tabs_row.css
that only activates under certain conditions. The condition that activates it is when all tabs no longer fit into one row. When all tabs would fit into one row, _separate_pinned_tabs_row.css
isn't needed because dragging still works.
Are you sure you tested the relevant cases?
It didn't appear to have any noticeable effect because I didn't try it with enough pinned tabs for it to really matter.
Also a thing I didn't understand previously was that you intended to make the tabs show on one line if they seem to fit.
Case A (inactive):
Case B (active, tabs no longer fit into one row):
Okay, now I see what you mean. I thought it depended on number of pinned tabs, but number of unpinned tabs seems equally (or even more?) important here. I experienced a scenario where unpinned tabs still wrapped to another row one by one just like how multi-row_tabs normally works. But opening more unpinned tabs made it eventually wrap them all to separate line.
So, what I see happening is that I can have all my pinned tabs on top row followed by few unpinned tabs in the top row, and then some unpinned tabs on another row. Then if I open a new (unpinned) tab, it may cause all unpinned tabs to reflow to second line.
I see, that sounds like a glitch caused by the formula calculating the margin. In my browser it works perfectly, but it seems that in your browser, the margin is too short for some reason I can't troubleshoot myself. So you have to open a few more tabs before the margin gets long enough to cause the first unpinned tab to wrap.
To clarify, this is how the margin works:
In your case, for reasons specific to your setup, the margin isn't long enough to envelop the last unpinned tab when it should (i.e. just before the wrap). Any tab that isn't enveloped will wrap individually.
Perhaps this is a compact mode vs. normal mode thing? Edit: No, it still works in normal mode.
For me it doesn't work in compact or normal mode. It also doesn't seem to matter whether --multirow-tab-dynamic-width
is 0
or 1
Regardless, this rather sounds like a hack that might work for specific setups but is not really possible to generalize. If I'm understanding it correctly (which I might not be), you would need two rules for every potential tab instance, so like if you wanted to support 100 tabs then you would need 100*2 rules.
So, this is totally not going to be included in multi-row_tabs.css and the current _separate_pinned_tabs_row_patch does exactly what it says pretty neatly.
If anything, this would need to be its own separate _patch file, but I would be hesitant to accept it since I don't really understand how it is supposed to work - especially since it doesn't seem to work for me like it works for you.
The idea was that one way to achieve the desired behavior would be to nest unpinned tabs together into a single element in the html, and the container would basically act like a single big tab that cannot be split across rows (rather than inserting a line break pseudo-element). It occured to me that since tabs have a minimum width, it is possible to emulate that behavior by using the first unpinned tab's right margin as a quasi-container. The first unpinned tab then effectively has the same width as all unpinned tabs taken together, and thus wraps at the correct times, pushing the other tabs with it. It's really not that complicated.
I don't think it depends on any specific setup. Literally the only specific thing it depends on is the effective minimum width of unpinned tabs. That's why it uses that variable.
There is another possible explanation for why it doesn't work in your case. The width could be calculated correctly, but the css assumes that the last unpinned tab is the 4th to last element in the html. Your browser's html might be different for some reason, so that more than three elements come after the last tab, so that the css misapprehends the number of unpinned tabs. Basically, nth-last-child(4) should always be the last tab, so if that's not the case in your setup, it breaks the css.
The 3 elements coming after the last tab in my browser:
This should be easy enough to fix. Pretty sure I can generalize to make it agnostic regarding the number of sibling elements coming after the last tab. Edit: I can't believe I didn't think of using :nth-last-of-type()
Try this:
tab[first-visible-unpinned-tab]:nth-last-of-type(2) { margin-right: calc(1 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(2) + tab { margin-left: calc((-1) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(3) { margin-right: calc(2 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(3) + tab { margin-left: calc((-2) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(4) { margin-right: calc(3 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(4) + tab { margin-left: calc((-3) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(5) { margin-right: calc(4 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(5) + tab { margin-left: calc((-4) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(6) { margin-right: calc(5 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(6) + tab { margin-left: calc((-5) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(7) { margin-right: calc(6 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(7) + tab { margin-left: calc((-6) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(8) { margin-right: calc(7 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(8) + tab { margin-left: calc((-7) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(9) { margin-right: calc(8 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(9) + tab { margin-left: calc((-8) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(10) { margin-right: calc(9 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(10) + tab { margin-left: calc((-9) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(11) { margin-right: calc(10 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(11) + tab { margin-left: calc((-10) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(12) { margin-right: calc(11 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(12) + tab { margin-left: calc((-11) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(13) { margin-right: calc(12 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(13) + tab { margin-left: calc((-12) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(14) { margin-right: calc(13 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(14) + tab { margin-left: calc((-13) * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(15) { margin-right: calc(14 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(15) + tab { margin-left: calc((-14) * var(--multirow-tab-min-width)) !important; }
If I'm understanding it correctly (which I might not be), you would need two rules for every potential tab instance, so like if you wanted to support 100 tabs then you would need 100*2 rules.
I do not think it is likely for anyone to fit 100 unpinned tabs in a single row in their browser window. Consider a 4KHD wide screen. With a minimum tab width of 100, that's at most 80 rules, certainly less in practice, and that's already an extreme edge case. People who would want to use this CSS will want to use it because they don't have enough space, and not if they have space for 40 unpinned tabs. Most of them will have 1920px wide screens, for which in my case 28 rules are enough. So more than 38 rules (2*1920/100) most likely won't be necessary.
But yes, it would me much preferable to find a way to generalize this somehow, and not have twice as many rules as you can fit unpinned tabs in a row.
It would be very easy to generalize if you could use the n of :nth-of-type(n) inside the rule, like so:
.tabbrowser-tab[first-visible-unpinned-tab]:nth-last-of-type(n+1) {
margin-right: calc(n * var(--multirow-tab-min-width)) !important;
}
Then two rules would cover all possible cases. But CSS doesn't seem to support that yet.
The 3 elements coming after the last tab in my browser:
This looks wrong. There should be an additional container element with id tabbrowser-arrowscrollbox-periphery
immediately after the last <tab>
element which contains tabs-newtab-button and closing-tabs-spacer.
Are you sure you are testing with up-to-date Firefox?
Yeah, that latest snip seems better, but at least on my system it kinda breaks when further tabs are created. Like, the non-pinned tabs go to the same row as pinned tabs once again. So if you are at limit of what fits a single line then all tabs are in that single line. Good. Open a new tab, they don't fit anymore, so all non-pinned tabs flow to second line. Good again. Then open a couple more tabs, the tabs flow back to filling the same row where pinned tabs are. Not good.
I think this is exactly what I mentioned earlier, like you would need to create a rule for each potential tab for it to work correctly. It's not enough to consider just pinned tabs because you are using :nth-last-of-type()
so any tabs you open "change what that n is". Maybe it would work better if you used :nth-of-type()
instead?
Open a new tab, they don't fit anymore, so all non-pinned tabs flow to second line. Good again. Then open a couple more tabs, the tabs flow back to filling the same row where pinned tabs are. Not good.
Ah yeah, I know what you mean. I actually think it's good for them to go back to the first line when they are wrapping anyway, and hence dragging is broken anyway, because it saves vertical space, but they should do so immediately when they fill a row. But the point at which they do this depends on where the rules stop applying. So the behavior ends up seeming somewhat arbitrary when that happens. You would have to know in advance the exact number of unpinned tabs that fit one row in.
I may be misunderstanding you, but it sounds like you would prefer if once the unpinned tabs have jumped to the next line, adding new tabs should never make it jump back. In that case you would need an unreasonably large number of rules yes.
Actually no you wouldn't. To achieve that behavior you can just put a rule at the end that inserts a line break once the maximum-accounted-for number of tabs is exceeded.
Seems like this addition would achieve the behavior you want:
tab[first-visible-unpinned-tab]:nth-last-of-type(n+15) { margin-right: calc( 15 * var(--multirow-tab-min-width)) !important; }
tab[first-visible-unpinned-tab]:nth-last-of-type(n+15) + tab { margin-left: calc((-15) * var(--multirow-tab-min-width)) !important; }
The margins can stay the same past that point. Really, all we need to make sure is that the margin grows to the width of the screen minus one unpinned tab width. Then it can stop growing.
Maybe it would work better if you used :nth-of-type() instead?
I think that would require the css to know the number of pinned tabs
edit: oops i forgot that i changed the variable name for brevity reasons in my own userChrome.css
There is definitely a way to achieve the behavior I want using vw
and mod()
. Unfortunately mod()
is extremely new and unsupported.
I'm working on a Waterfox port of the multi-row tab stylesheet, and I found a partial workaround for the problem of not being able to drag tabs when they are on multiple rows.
The idea is that once tabs exceed the width of the first line, all unpinned tabs are moved to the next line together. Since dragging for pinned and unpinned tabs is handled separately, this assures that dragging works for as long as possible. This will be most useful for people who have a lot of pinned tabs.
The way this works is that all pinned and unpinned tabs each are assigned a certain percentage of the full width of the tab bar, allowing for mathematical predictability. Then a fairly clunky selector can account for all possible line-breaking cases and insert a line break in the right place once those conditions are met.
Only the bottom part of the stylesheet matters: https://github.com/sonofevil/waterfox-csshacks/blob/patch-1/Waterfox/chrome/multi-row_tabs.css
I haven't tested this with Firefox, but it should be easy to port it back.