Trying to understand GTK4-RS model binding

Hey there,

I've been following this tutorial on the GTK4-RS library.

On the template bellow they declared a tasks attribute, and on the impl block they create a method for accessing it that returns a clone the value inside the RefCell. If I understood correctly, this object is a Model List, when a user submits an input the program will create a new model and store it in this list which will be used to update the view.

What I don't understand and will highlight in the code bellow is; whenever a new model is appended to the list, its actually appending to a clone of it, not a reference the actual list that is used to update the view, and yet the view gets updated. What is going on here, is it something particular to this library implementation ?

// Object holding the state
#[derive(CompositeTemplate, Default)]
#[template(resource = "/org/gtk_rs/Todo1/window.ui")]
pub struct Window {
    #[template_child]
    pub entry: TemplateChild<Entry>,
    #[template_child]
    pub tasks_list: TemplateChild<ListView>,

// This is the model list
    pub tasks: RefCell<Option<gio::ListStore>>,
}

This is the method that returns the clone

fn tasks(&self) -> gio::ListStore {
    // Get state
    self.imp()
        .tasks
        .borrow()
        .clone()
        .expect("Could not get current tasks.")
}

This is where the model list is bound to the view

  fn setup_tasks(&self) {
      // Create new model
      let model = gio::ListStore::new(TaskObject::static_type());

      // Get state and set model
      self.imp().tasks.replace(Some(model));

      // Note that here they're using the `tasks()` method that returns a
     // clone of the model.
      let selection_model = NoSelection::new(Some(self.tasks()));
      self.imp().tasks_list.set_model(Some(&selection_model));
  }

The two methods bellow are used to update the model with the user input

// Listening for the signal
    fn setup_callbacks(&self) {
        // Setup callback for activation of the entry
        self.imp()
            .entry
            .connect_activate(clone!(@weak self as window => move |_| {
                window.new_task();
            }));

        // Setup callback for clicking (and the releasing) the icon of the entry
        self.imp().entry.connect_icon_release(
            clone!(@weak self as window => move |_,_| {
                window.new_task();
            }),
        );
    }
// Actually creating the new model
    fn new_task(&self) {
        // Get content from entry and clear it
        let buffer = self.imp().entry.buffer();
        let content = buffer.text().to_string();
        if content.is_empty() {
            return;
        }
        buffer.set_text("");

        // Add new task to model
        let task = TaskObject::new(false, content);

        // Here it is, appending to a clone, doesn't update the list that's
       // actually beeing watched anywhere
        self.tasks().append(&task);
    }

Also note that these setup methods only run when the window is created, essentially, they run when the program starts and never again.

// Trait shared by all GObjects
impl ObjectImpl for Window {
    fn constructed(&self) {
        // Call "constructed" on parent
        self.parent_constructed();

        // Setup
        let obj = self.obj();
        obj.setup_tasks();
        obj.setup_callbacks();
        obj.setup_factory();
    }
}

Most of the types in GTK, GLib, and Gio are wrappers around pointers obtained from their respective C libraries. The actual objects in those libraries are reference counted, so you can think of gio::ListStore as being an Arc<gio::ListStore> where calling Clone increments the reference count rather than creating a new object

4 Likes