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

core: Add TypedListView type #440

Merged
merged 13 commits into from
May 8, 2023
Merged

core: Add TypedListView type #440

merged 13 commits into from
May 8, 2023

Conversation

AaronErhardt
Copy link
Member

@AaronErhardt AaronErhardt commented Mar 30, 2023

Adds a TypedListView type as a high level abstraction over gtk::ListView.

The goal is to simplify the workflow regarding the following points:

  • Type-safety and ergonomic interfaces: no manual type casting needed
  • Idiomatic: Just implement a trait and you're ready to go
  • Easy sorting: Enable sorting by deriving or implementing the Ord trait
  • Easy filtering: Add multiple filters and enable or disable them individually

This won't be a solution for everyone because it isn't 100% as flexible as the raw bindings.
Yet, for most use-cases this should provide a massive reduction of boilerplate code and makes the implementation less error-prone.
The types_list_view example shows a small app with a sortable and filterable list view.

For now, it is still to be decided whether this type is merged here or will be included into gtk-rs directly.

It's basically slightly more than a proof-of-concept by now, but not complete. The API can still change in the future.

Checklist

  • cargo fmt
  • cargo clippy
  • cargo test
  • updated CHANGES.md

Example with async

Screencast.from.2023-04-02.20-32-17.webm

@AaronErhardt AaronErhardt changed the title core: Add experimental ListViewWrapper type core: Add TypedListView type Apr 2, 2023
@AaronErhardt AaronErhardt marked this pull request as ready for review April 10, 2023 00:43
@AaronErhardt
Copy link
Member Author

With seemingly no interest from the gtk-rs side to merge this into their crates, I suggest to merge this here first and then move it to gtk-rs later if desired.

@AaronErhardt AaronErhardt added the waits-on-review-medium A PR that's not trivial to review but also doesn't require in depth knowledge label Apr 10, 2023
@FSMaxB
Copy link

FSMaxB commented Apr 15, 2023

I'm not fully familiar with all the ins and outs of Relm4, but would it somehow be possible to get a typed Gio.ListModel instead of ListStore, so you can generate list elements on the fly with rust code instead of storing all of them in the ListStore ahead of time?

@AaronErhardt
Copy link
Member Author

I'm not fully familiar with all the ins and outs of Relm4

Regarding Relm4, it is basically an abstraction layer over gtk-rs that tries to make everything as idiomatic as possible. In fact, you can mix Relm4 and gtk-rs as much as you want, there's nothing really special happening under the hood.

would it somehow be possible to get a typed Gio.ListModel

I think that should be possible. I just didn't think so far to be honest. So if you have an idea, I would be interested.

@FSMaxB
Copy link

FSMaxB commented Apr 16, 2023

So if you have an idea, I would be interested.

Basically what I'm doing is to use a SQlite database as a ListModel directly, which also means that it has to handle sorting and filtering instead of those being separate (although I haven't actually implemented that part yet).

I made a trait that can be implemented like so (ignore the async for now, I basically do a runtime.block_on_future to run them, which isn't really ideal, but it's fast enough in my use case):

#[async_trait]
pub trait DatabaseView: Send + Sync + 'static {
	type RustModel: Send;
	type GObjectModel: StaticType + ObjectType + IsA<Object> + From<Self::RustModel>;

	async fn read_at_offset(database_pool: &SqlitePool, offset: u32) -> anyhow::Result<Self::RustModel>;
	async fn count(database_pool: &SqlitePool) -> u32;
}

#[async_trait]
trait DynamicDatabaseView {
	fn rust_model(&self) -> TypeId;
	fn gobject_model(&self) -> glib::Type;

	async fn read_at_offset(&self, database_pool: &SqlitePool, offset: u32) -> anyhow::Result<Box<dyn Any + Send>>;
	async fn count(&self, database_pool: &SqlitePool) -> u32;

	fn convert(&self, rust_model: Box<dyn Any>) -> Object;
}

#[async_trait]
impl<T: DatabaseView> DynamicDatabaseView for T {
	fn rust_model(&self) -> TypeId {
		TypeId::of::<T::RustModel>()
	}

	fn gobject_model(&self) -> glib::Type {
		T::GObjectModel::static_type()
	}

	async fn read_at_offset(&self, database_pool: &SqlitePool, offset: u32) -> anyhow::Result<Box<dyn Any + Send>> {
		let model = T::read_at_offset(database_pool, offset).await?;
		Ok(Box::new(model))
	}

	async fn count(&self, database_pool: &SqlitePool) -> u32 {
		T::count(database_pool).await
	}

	fn convert(&self, rust_model: Box<dyn Any>) -> Object {
		let rust_model = *rust_model.downcast::<T::RustModel>().expect("Incorrect type");
		let gobject_model = T::GObjectModel::from(rust_model);
		gobject_model.upcast()
	}
}

A user has to implement DatabaseView which then automatically implements DynamicDatabaseView to handle the type erasure. If you replace SqlitePool with some generic type, something similar to this could extend to any kind of data source, not just a database. You would probably need one or more additional type parameters and methods to control sorting and filtering.

Based on that, I have implemented a custom GObject subclass that implements the ListModel interface by calling methods from the DynamicDatabaseView. Once sorting and filtering is added, it will also need to emit the items-changed signal whenever the sort order etc. changes (and also somehow when the underlying data changes).

I know this is very much still in a prototype in the "just get it to work" stage, but that's the general direction I've been going so far.

@AaronErhardt
Copy link
Member Author

Sounds very interesting. I really like the idea that you can implement your own storage backend. We could offer a default in-memory-store based on something like VecDeque and could allow users to use their own database backend. Let me know if I can help you!

Btw. something similar was attempted in relm4-store, but has been abandoned by its maintainer for quite some time now. Maybe you can find some clues there: https://github.com/mskorkowski/relm4-store

Copy link

@malramsay64 malramsay64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have had a look through and there are no issues that stand out to me, although I should note that I don't completely understand the interactions with GTK.

@AaronErhardt
Copy link
Member Author

I know that this is not the most generic and powerful abstraction yet, but I'd like to merge this for now and continue tuning this feature in the future.

@AaronErhardt AaronErhardt merged commit 2c83e12 into main May 8, 2023
6 checks passed
@AaronErhardt AaronErhardt deleted the list-view-wrapper branch May 8, 2023 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waits-on-review-medium A PR that's not trivial to review but also doesn't require in depth knowledge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants