iced icon indicating copy to clipboard operation
iced copied to clipboard

PickList: Use a closure instead of the toString trait

Open itytophile opened this issue 3 years ago • 7 comments

Hi, in my iced app I use a localization system to translate options inside pick lists. However, Picklist only relies on the toString trait to display its options. So for the translation to happen, my Display trait implementation must check a global variable at each call:

impl Display for Armor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.apply_locale(&*crate::LOCALE.lock().unwrap()))
    }
}

LOCALE is the global variable (I use once_cell). In my opinion, this is not very pretty to check a global variable inside an implementation of such a trait.

There is another limitation, I can't implement the Display trait for a Struct from an external crate. What I would like is to be able to give a closure to the PickList struct to handle converting options into String (maybe Fn(T) -> String or another trait like PickListDisplay?).

Is this possible? I could try implementing it if you think this is. Thank you for maintaining iced by the way.

itytophile avatar Jun 19 '21 14:06 itytophile

I think you might be barking up the wrong tree with the global variable.

What if in your state the picklist values are stored in an enum, something like

enum PickListState {
    Engligh(SomeStruct),
    French(SomeStruct)
    ~~ SNIP
}

Then in your view wrap the PickList in a helper function so rather than calling PickList::new() create a function called locale_pick_list() that looks something like this

fn locale_pick_list(state: PickListState) -> PickList {
    match state {
        English(s) => {PickList::new(..)}
        French(s) => {PickList::new(..)}
    ~~ SNIP
    }
}

Just an idea

13r0ck avatar Jun 21 '21 17:06 13r0ck

Instead of a global variable, you could simply make a wrapper type that takes in a handle for your locale handler (wrapped in Arc so you can clone it however you want). This type could look like this:

struct Wrapper<K: Key> {
	locale: Arc<LocaleHandler>,
	key: K,
}

trait Key {
	fn get_key(&self) -> &str;
}

Then you could implement Display for it:

impl<K: Key> Display for Wrapper<K> {
	fn fmt(&self, f: Formatter) -> Result {
		f.write_str(self.locale.get_string(self.key.get_key()))
	}
}

Then you could just implement Key for whatever you want, pass it into the Wrapper, and pass that into to PickList.

yusdacra avatar Jun 21 '21 17:06 yusdacra

Thanks for your answers. Yesterday, I made a wrapper struct to implement the Display trait just like @yusdacra 's example and that worked. But using an Arc to avoid the global variable is a very interesting idea.

I don't know if I understand @13r0ck 's suggestion correctly. Your idea is to use an enum (with each language as a variant) to wrap the external struct? It seems that we could completely avoid the locale variable in this case. Unfortunately, in my case the program knows the languages only at runtime (so no enum) but I think we can find a workaround with the same spirit.

Thanks -again- for your help!! But I still think that a closure would be interesting.

itytophile avatar Jun 21 '21 18:06 itytophile

Thanks -again- for your help!! But I still think that a closure would be interesting.

Yeah I do agree that a closure would provide an easier way to do this. You can experiment with a custom widget, by copying the necessary PickList code from iced, and make your own widget. Or if that's too much hassle, you could directly modify the PickList widget in iced and see how that works out.

I think an interface like this could be interesting:

let display_item = |item| item.to_string();
PickList::new(state, selected_item, items, display_item)

yusdacra avatar Jun 21 '21 18:06 yusdacra

In that case why would a closure even be necessary, when it is already possible to apply a function to the items in the view?

13r0ck avatar Jun 21 '21 18:06 13r0ck

In that case why would a closure even be necessary, when it is already possible to apply a function to the items in the view?

A closure allows it to be lazily evaluated; eg. if we don't show the menu part of the pick list, we wouldn't need to convert the items to text.

yusdacra avatar Jun 21 '21 18:06 yusdacra

ooh gotcha. Thanks!

13r0ck avatar Jun 21 '21 18:06 13r0ck