Aylur / ags

A customizable and extensible shell
GNU General Public License v3.0
2.11k stars 109 forks source link

Widget.Stack with delayed children #392

Closed dawsers closed 5 months ago

dawsers commented 5 months ago

I am trying to modify my media player widget to use a Widget.Stack so I can manage multiple mpris.players more easily.

I have found something that is not very intuitive, and I cannot really consider it a bug due to my ignorance about Gtk. I started writing some widgets for AGS this weekend, and I find it really cool. I am sure someone here will be able to make sense of it easily. Thank you in advance.

I have included a toy example that tries to show the problem I am having.

The problem is when I create a MediaPlayerBox, the player names show up at the bar normally, and they update as I create and change players.

Instead, when I create a MediaPlayerStack, the buttons never show up, even though I can inspect the graph (ags -i) and see they are there, but they are never mapped or made visible (their sizes are zero).

function MPlayer(player) {
    const label = Widget.Label();
    label.label = player.bus_name;

    return Widget.Button({
        class_name: 'media-test',
        child: label,
    });
}

function create_children_for_box(players) {
    let cs = [];
    for (let p of players) {
        console.log(`Created player ${p.bus_name}`);
        cs.push(MPlayer(p));
    }
    return cs;
}

function MediaPlayerBox() {
    return Widget.Box({
        setup: self => {
            mpris.connect('player-changed', (mpr, bus) => {
                print("Changing to player ", bus);
            });
            mpris.connect('player-added', (mpr, bus) => {
                print("Added player ", bus);
            });
        },
        children: mpris.bind('players').as(players => create_children_for_box(players)),
    });
}
function create_children_for_stack(players) {
    let cs = {};
    for (let p of players) {
        console.log(`Created player ${p.bus_name}`);
        cs[p.bus_name] = MPlayer(p);
    }
    return cs;
}
function MediaPlayerStack() {
    return Widget.Stack({
        setup: self => {
            mpris.connect('player-changed', (mpr, bus) => {
                print("Changing to player ", bus);
                self.shown = bus;
            });
            mpris.connect('player-added', (mpr, bus) => {
                print("Added player ", bus);
                self.shown = bus;
            });
        },
        children: mpris.bind('players').as(players => create_children_for_stack(players)),
    });
}

I have tried all kinds of CSS tricks, like setting min-width etc.

I also created another example where the children are initialized without delay, and it seems to work. Of course, that is useless in this case.

function MediaPlayerTest() {
    let player1 = [], player2 = [];
    player1.bus_name = 'test1';
    player2.bus_name = 'test2';
    let mplayers = [ player1, player2 ];

    function create_stack_children(players) {
        let cs = {};
        for (let p of players) {
            console.log(`Created player ${p.bus_name}`);
            cs[p.bus_name] = MPlayer(p);
        }
        return cs;
    }

    let active_player = Variable('test1');

    Utils.interval(1500, () => {
        let player = active_player.getValue();
        console.log(player);
        if (player == 'test1') {
            player = 'test2';
        } else {
            player = 'test1';
        }
        active_player.setValue(player);
    });

    return Widget.Stack({
        setup: self => {
            self.hook(active_player, () => {
                let player = active_player.value.toString();
                print("Changing player");
                self.shown = player;
            })
        },
        children: create_stack_children(mplayers)
    })
}

Any help will be greatly appreciated!

Aylur commented 5 months ago

I am not sure why the stack example doesn't work, the code looks good to me but this imperative approach is not what I would suggest you to do have a look at the example on the wiki https://aylur.github.io/ags-docs/services/mpris/#example-widget if you don't need animations from the stack, a box can work for you the same you should have a main box that binds mpris.players and passes down the player objects to individual widgets

dawsers commented 5 months ago

Thanks for your help!

In the end I couldn't figure out why the stack wasn't working, so I used a Box and a menu to select which one of the player widgets inside the Box is visible (turning visibility for the others to false). It works well, the only problem is when a Box is first created, all its children start visible. It is not a problem, just aesthetically not ideal.

Thanks again for your help. I am enjoying the framework very much. It would be great to have some space in the wiki for people to share Widgets so we can reuse things and see how people solved certain problems, like a library of solutions.

If you want to close the issue, of course feel free. I just wanted to leave some trace for people who are in the same situation.

Aylur commented 5 months ago

when a Box is first created, all its children start visible. It is not a problem, just aesthetically not ideal

Its a known issue in some cases yes, this is because gtk3 widgets are invisible by default and so the window calls show_all() which makes every children visible recursively on construct, so setting visible: false has no effect It will be fixed in some upcoming version, I know a way to fix this now A workaround you can currently do one of

setup: self => Utils.idle(() => self.visible = false),
setup: self => Utils.timeout(100, () => self.visible = false),

it can throw an error if the widget is destroyed before the callback execution in which case do this

setup: self => Utils.timeout(100, () => {
  if (!self.is_destroyed)
    self.visible = false
}),

It would be great to have some space in the wiki for people to share Widgets so we can reuse things and see how people solved certain problems, like a library of solutions.

I plan to do something on that front too, but before that I have something else that I am working on see #297

Noname672 commented 2 months ago

I encoutered a similarly problem today when trying to subclass a widget. It turned out the bug is due to the Stack's children setter lacking the show_all() call. It can be worked around by adding this to your stack's setup function

self.connect("notify::children", () => self.show_all())