How to create a vector with different types of GUI widgets

I am trying to create a GUI application using fltk which will have 4 types of input widgets: checkbox, menu choice as well as single and multi-line input text fields:

All these have a function named value() which returns the entered value.

Can I have a vector which can hold these different types of widgets. I want to then iterate through the vector and get values from each of these widgets:

for v in mixed_vector{
   let entered_value = v.value();
   // do something with entered_value..
}

I see a page on net but not able to apply it to my problem.

At another page, enum is used:

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operator {
    Add,
    Sub,
    Mul,
    Div,
}

fn main() {
    let mut output: Vec<String> = Vec::new();  // create an output vector
    let a = 2;
    let b = Operator::Add;
    let c = 3;
    output.push(a.to_string());
    output.push(b.to_string());
    output.push(c.to_string());
}

What is the best solution for my problem? Thanks for your insight.

Looks to me like the value method comes from the ButtonExt trait. You can create a vector of trait objects where each element implements ButtonExt[1], thus allowing you to call the value method on each element.

Pseudo-code:

let elements: Vec<Box<dyn ButtonExt>> = vec![
    Box::new(MultilineInput::new()), 
    Box::new(Input::new())
];

for v in elements {
    v.value();
}

Never mind, ButtonExt requires the WidgetExt trait as supertrait. WidgetExt has methods with generic type parameters, hence it is not object safe. AFAIK that leaves you with implementing an enum wrapper with a variant for each element you want to store in the Vector and forward the value method to the widgets, something along the lines of:

enum Widgets {
    MultilineInput(MultilineInput),
    Input(Input),
    // ...
}

impl Widgets {
    fn value(&self) -> bool {
        match self {
            Self::MultilineInput(mi) => mi.value(),
            Self::Input(i) => i.value(),
            // ...
        }
    }
}

let elements: Vec<Widget> = vec![
    Widget::MultilineInput(MultilineInput::new()), 
    Widget::Input(Input::new())
];

for v in elements {
    v.value();
}

  1. If ButtonExt is object safe which, on a quick glance, look to me like it is. ↩ī¸Ž

1 Like

I see here that value function is part of impl ShortcutButton. Is it of any relevance?

enum widgets ..... impl widgets... is an easy to understand solution. Thanks.

I agree that the enum is the right thing to do, but the value methods are implemented on independent traits and each has a different return type:

// CheckButton: ButtonExt
fn value(&self) -> bool

// Choice: MenuExt
fn value(&self) -> i32

// Input: InputExt
// MultipleInput: InputExt
fn value(&self) -> String

To return these varying types, you will need another enum that mirrors enum Widgets.

enum Widgets {
    CheckButton(CheckButton),
    Choice(Choice),
    MultilineInput(MultilineInput),
    Input(Input),
}

enum WidgetValues {
    CheckButton(bool),
    Choice(i32),
    MultilineInput(String),
    Input(String),
}

impl Widgets {
    fn value(&self) -> WidgetValues {
        match self {
            Self::CheckButton(cb) => WidgetValues::CheckButton(cb.value()),
            Self::Choice(c) => WidgetValues::Choice(c.value()),
            Self::MultilineInput(mi) => WidgetValues::MultipleInput(mi.value()),
            Self::Input(i) => WidgetValues::Input(i.value()),
        }
    }
}

And then handle unwrapping on the other end. Or provide duck-typing methods on WidgetValues that does the unwrapping internally:

impl WidgetValues {
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Self::CheckButton(cb) => Some(*cb),
            _ => None,
        }
    }

    pub fn as_i32(&self) -> Option<i32> {
        match self {
            Self::Choice(c) => Some(*c),
            _ => None,
        }
    }

    pub fn as_str(&self) -> Option<&str> {
        match self {
            Self::MultilineInput(i) | Self::Input(i) => Some(i),
            _ => None,
        }
    }
}
1 Like

Important and necessary complication!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.