iced
iced copied to clipboard
PickList: Use a closure instead of the toString trait
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.
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
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.
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.
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)
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?
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.
ooh gotcha. Thanks!