Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example for working with GtkStack and GtkStackSwitcher #40

Open
gsurrel opened this issue Apr 30, 2020 · 2 comments
Open

Add example for working with GtkStack and GtkStackSwitcher #40

gsurrel opened this issue Apr 30, 2020 · 2 comments

Comments

@gsurrel
Copy link

gsurrel commented Apr 30, 2020

I am trying to use vgtk to create a non-trivial app. I did an initial design using Glade, and I understand that it cannot be used as it in vgtk. Therefore, I try to recreate it, but I am stuck when needing to construct the part with GtkStack and GtkStackSwitcher: I can't find the way to connect the two together. Maybe I missed something from the documentation or tutorial?

I made a minimal example using Glade here:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow">
    <property name="can_focus">False</property>
    <property name="default_width">440</property>
    <property name="default_height">250</property>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="show_close_button">True</property>
        <child>
          <placeholder/>
        </child>
        <child type="title">
          <object class="GtkStackSwitcher">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="stack">stack1</property>
          </object>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkStack" id="stack1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="transition_type">slide-left-right</property>
        <child>
          <object class="GtkLabel">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Stack page 0</property>
          </object>
          <packing>
            <property name="title" translatable="yes">Page 0 title</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Stack page 1</property>
          </object>
          <packing>
            <property name="title" translatable="yes">Page 1 title</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Screenshot from 2020-04-30 16-14-54

@nt8r
Copy link
Contributor

nt8r commented Jun 6, 2020

I don't think this is really possible at present. It would be trivial if vgtk had a capacity to bind names in the gtk! macro, for example by handling assignments to id specially.

In the meantime, you could perhaps expand the gtk! macro manually and see if you can get a reference to the constructed GtkStack there to pass to the switcher.

@jocelyn-stericker
Copy link

Disclaimer: The following is a hacky and evil way of handling indirect (not parent-child) relationships between Widgets using vgtk's current feature set. I have not written any gtk or vgtk before last week. If you use this and something breaks, I will laugh at you.

Assumptions:

  • you have two Widgets you want to somehow connect (e.g., view_switcher.set_stack(Some(stack))).
  • both widgets are rendered initially, and are never re-arranged, changed, or removed until the Component is unmounted.
  • the parents of these Widgets, within the Component, are not ever re-arranged, changed, or removed.

Steps:

  1. add the following blasphemy to src/escape_hatch.rs or whatever:
use std::collections::HashMap;
use vgtk::lib::glib::object::{CanDowncast, Cast, IsA, ObjectType};
use vgtk::lib::gtk::{Container, ContainerExt, Widget, WidgetExt};

pub trait GetNamedDescendants {
    /// Loop through all container children of any depth and create a
    /// map from widget_name to Widget instance.
    fn get_named_descendants(&self) -> HashMap<String, Widget>;
}

impl<O: IsA<Widget>> GetNamedDescendants for O {
    fn get_named_descendants(&self) -> HashMap<String, Widget> {
        let mut map = HashMap::new();
        let mut widgets: Vec<Widget> = vec![self.upcast_ref::<Widget>().clone()];

        while let Some(widget) = widgets.pop() {
            let name = widget.get_widget_name();
            if !name.is_empty() {
                map.insert(name.to_string(), widget.clone());
            }

            // If you would like to support other kinds of parent-child relationships
            // in this hack, other than Container children, you will need to add support here.
            if let Some(widget) = widget.downcast_ref::<Container>() {
                for child in widget.get_children() {
                    widgets.push(child);
                }
            }
        }

        map
    }
}

/// This is just syntactic sugar because the casts get annoying.
pub trait GetAsWidgetType {
    fn get_downcast<T: ObjectType>(&self, name: &str) -> Option<&T>
    where
        Widget: CanDowncast<T>;
}

impl GetAsWidgetType for HashMap<String, Widget> {
    fn get_downcast<T: ObjectType>(&self, name: &str) -> Option<&T>
    where
        Widget: CanDowncast<T>,
    {
        self.get(name).and_then(|val| val.downcast_ref::<T>())
    }
}

The remaining steps use the libhandy ViewSwitcher, but this approach should work for GtkStackSwitcher.

  1. give widget_names to the widgets you want to reference (e.g., <ViewSwitcher widget_name="dogbook_view_switcher" />). widget_names are usually used for CSS. We're abusing them here.
  2. add an on show handler to the topmost Widget and create the relationships in that handler:
use crate::escape_hatch::*;

...

<Window
    on show=|win| {
        let named_objects = win.get_named_descendants();
        let stack: &Stack = named_objects.get_downcast("dogbook_stack").unwrap();
        let view_switcher: &ViewSwitcher = named_objects
            .get_downcast("dogbook_view_switcher")
            .unwrap();
        let view_switcher_bar: &ViewSwitcherBar = named_objects
            .get_downcast("dogbook_view_switcher_bar")
            .unwrap();
        view_switcher.set_stack(Some(stack));
        view_switcher_bar.set_stack(Some(stack));
        Message::None
    }
>

$0.02 CAD: vgtk may be able to implement something based loosely on the React Ref object. The collection of References for a given component should be an associated type of that component, like Message & Properties. The References object is passed as a parameter to view() so that they can be bound to Objects. The tricky part will be figuring how where and when to expose the bound References.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants