xilem icon indicating copy to clipboard operation
xilem copied to clipboard

`enum` state is very unergonomic (`lens` ergonomics?)

Open axelkar opened this issue 1 month ago • 5 comments

Example:

struct AState;
impl AState {
    fn ui_logic(&mut self) -> impl WidgetView<Self> + use<> {
        label("A")
    }
}

struct BState;
impl BState {
    fn ui_logic(&mut self) -> impl WidgetView<Self> + use<> {
        label("B")
    }
}

enum MyState {
    A(AState),
    B(BState)
}
impl MyState {
    fn ui_logic(&mut self) -> impl WidgetView<Self> + use<> {
        use xilem_core::one_of::Either;

        match self {
            Self::A(_a) => Either::A(lens(AState::ui_logic, |state: &mut Self| match state {
                Self::A(a) => a,
                _ => unreachable!(),
            }))
            Self::B(_b) => Either::B(lens(BState::ui_logic, |state: &mut Self| match state {
                Self::B(b) => b,
                _ => unreachable!(),
            }))
        }
    }
}

The same goes for matching Options and Results.

axelkar avatar Oct 14 '25 18:10 axelkar

_a and _b also go unused. Is that desired? Should map_state be used instead for efficiency?

axelkar avatar Oct 14 '25 18:10 axelkar

See also:

  • You can't "split" a lens, i.e. you can't have a lens from AppState so the child can access both state.a and state.b.
  • Matching on enums in lenses is unergonomic (and also currently broken - see #xilem > Incoherent state issues), because you can't avoid the lens needing to know the type of the enum. There are possible janky solutions, but I've not reasoned about them carefully.

https://github.com/linebender/xilem/pull/1032#issuecomment-3034983835

axelkar avatar Oct 16 '25 15:10 axelkar

Related to lensing multiple states: xilem_core::map_action and returning an action from button()'s callback seems to work as an alternative to passing multiple states via impl WidgetView<State>.

axelkar avatar Oct 16 '25 16:10 axelkar

Yeah, we don't have a good answer here at the moment; I agree that it's very rough, though.

DJMcNab avatar Oct 20 '25 16:10 DJMcNab

For druid I did some exploration, in case you're interested: https://github.com/linebender/druid/issues/1135.

  • I concluded that druid-enums (https://github.com/linebender/druid/issues/1135#issuecomment-716332150) was better;
  • There was also a comparison to switcher (https://github.com/linebender/druid/issues/1135#issuecomment-827673747);
  • I think mixing lenses and prisms (https://github.com/linebender/druid/issues/1135#issuecomment-678936891) is still cool XD

swfsql avatar Oct 28 '25 02:10 swfsql

Not sure that I found a right place, but went into similar issue:

fn main_view(dizola: &mut Dizola) -> impl WidgetView<Dizola> + use<> {
    lens(
        |state| match state {
            ImState::Initial => OneOf2::A(label("LETSGO!")),
            ImState::Dictionary(dictionary_state) => OneOf::B(dictionary_view(dictionary_state)),
        },
        |dizola: &mut Dizola| &mut dizola.state,
    )
}

when I try to draw a view for enum with inner state, it breaks the xilem:


error[E0277]: the trait bound `impl WidgetView<DictionaryState>: View<ImState, (), ViewCtx>` is not satisfied
  --> gui/src/lib.rs:43:38
   |
43 |   fn main_view(dizola: &mut Dizola) -> impl WidgetView<Dizola> + use<> {
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `View<ImState, (), ViewCtx>` is not implemented for `impl WidgetView<DictionaryState>`
44 | /     lens(
45 | |         |state| match state {
46 | |             ImState::Initial => OneOf2::A(label("LETSGO!")),
47 | |             ImState::Dictionary(dictionary_state) => OneOf::B(dictionary_view(dictionary_state)),
48 | |         },
49 | |         |dizola: &mut Dizola| &mut dizola.state,
50 | |     )
   | |_____- return type was inferred to be `Lens<{[email protected]:45:9}, OneOf<Label, ..., ..., ..., ..., ..., ..., ..., ...>, ..., ..., ..., _, ...>` here

The problem (or my lack of xilem knowledge) is that


fn dictionary_view(dictionary_state: &mut DictionaryState) -> impl WidgetView<ImState> + use<> {
    label("Dictionary")
}

can't return DictionaryState generic of WidgetView, it only "captures" ImState enum as a whole.

humb1t avatar Dec 05 '25 22:12 humb1t

I wonder if we should create a meta-issue of the current ergonomic issues of Xilem. I think we need a bird's eye view of these problems.

PoignardAzur avatar Dec 07 '25 17:12 PoignardAzur

It looks like you could use something like Ambassador (But I am not a Xilem expert)

https://github.com/hobofan/ambassador

nielsle avatar Dec 07 '25 18:12 nielsle