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

typed Generics possible? #2

Closed
1sra3l opened this issue Nov 14, 2021 · 14 comments
Closed

typed Generics possible? #2

1sra3l opened this issue Nov 14, 2021 · 14 comments

Comments

@1sra3l
Copy link
Contributor

1sra3l commented Nov 14, 2021

I know this is likely fringe, but is this even possible?

#[derive( Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "fltkform", derive(FltkForm))]
pub struct Config<T:Copy 
                 + Default
                 + AddAssign
                 + Add<Output = T>
                 + Div<Output = T>
                 + DivAssign
                 + Mul<Output = T>
                 + MulAssign
                 + Neg<Output = T>
                 + Rem<Output = T>
                 + RemAssign
                 + Sub<Output = T>
                 + SubAssign
                 + std::cmp::PartialOrd> {
    /// Identification Number
    pub id:T,
    pub cost:T,
    pub amount:T,
}
@MoAlyousef
Copy link
Collaborator

MoAlyousef commented Nov 14, 2021

It should be implementable I think, never done it before though. But currently it won't work.

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 14, 2021

😮 That would be cool if it was possible. Is there a way the end user can easily create their own? Could one just make an impl for anything they choose? If this is the case, simply documenting that process would address this issue in the future for most other future errors with types not existing, and people could just send you their impl which you could decide if it is good enough, or worth including.

Part of my goal as you probably can tell is to remake the "character-maker" program and make it work with rpgstat, and this will eventually make it into "Rust Hero" as the interface to configure the actual things.

Oh, I just thought of another use case:
Someone wants only non-editable *Output rather than *Input. Is there a way to choose this? I think some may want to use it only to display things, like a "manifest" where they could just go from file to file and not mess it up, but copy things to paste elsewhere if they need.

Thanks for being into making this, I find this possibility so fascinating and almost endless!

When it is all done you can say character-maker uses this 😺 as the way to generate the interface. And eventually Rust Hero as well.

@MoAlyousef
Copy link
Collaborator

You, you could just impl FltkForm for any type of your choosing, then return a Box::new(some_widget).
I've added a new trait method, fn view(), which basically allows viewing (without modifying the generated gui):

let form = my_struct.view();

or using the form widget:

let form = Form::default().from_data_view(my_struct);

That means, if you would like, you'd have to implement both methods of FltkForm (generate and view).

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 18, 2021

Ok here is where I am getting stuck:

error[E0119]: conflicting implementations of trait `FltkForm` for type `std::vec::Vec<_>`
   --> fltk-form/src/lib.rs:395:1
    |
301 | / impl<T> FltkForm for Vec<T>
302 | | where
303 | |     T: FltkForm,
304 | | {
...   |
326 | |     }
327 | | }
    | |_- first implementation here
...
395 | / impl<T:Copy 
396 | |     + Default
397 | |     + AddAssign
398 | |     + Add<Output = T>
...   |
426 | |     }
427 | | }
    | |_^ conflicting implementation for `std::vec::Vec<_>`

Basically how do I do <T:NumbersOnly> to avoid this issue?

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 18, 2021

BTW I had to make a <T> with + Neg<Output = T> and one without out it, which removes all the f64,f32,...,u8 code.
Is there a "primitive numbers only" derive?

@MoAlyousef
Copy link
Collaborator

There are no such derives or bounds in the standard library. There is a crate called num which provides such bounds and numeric traits.

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 18, 2021

num is the reason I am even able to do a Generic Type stat library (rpgstat) 😄 It is very handy!
How can you differentiate between a vector and any other comparable type? What does a vector need that numbers do not?

@MoAlyousef
Copy link
Collaborator

MoAlyousef commented Nov 18, 2021

Vecs (and other collections) implement the Iterator trait, so it might be used as a trait bound. Not sure how that would compose since Rust currently doesn’t have generic specialization.

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 19, 2021

I have been on the Rust chat channel and cannot come up with a good solution for this. As far as I can tell it is not fully possible to do what I want, as Vectors continue to interfere with numbers trait bounds. The workaround that "works" (read: compiles) is this:

pub trait Primitive {}

macro_rules! primitive {
    ($($type:ty),*) => {
        $(impl Primitive for $type {})*
    }
}

primitive!(i8, u8, i16, u16, i32, f32, i64, f64, isize, usize);
//...
impl<T:Copy
    + Primitive 
    + Default
    + Debug // could use Display {} instead
    + num::NumCast> FltkForm for T {
    fn generate(&self) -> Box<dyn WidgetExt> {
        let mut i = input::FloatInput::default();
        let val = format!("{:?}", *self);
        i.set_value(&val);
        unsafe {
            i.set_raw_user_data(transmute(1_usize));
        }
        Box::new(i)
    }
    fn view(&self) -> Box<dyn WidgetExt> {
        let mut i = output::Output::default();
        let val = format!("{:?}", *self);
        i.set_value(&val);
        unsafe {
            i.set_raw_user_data(transmute(1_usize));
        }
        Box::new(i)
    }
}
//...

But it still fails with vectors when using with rpgstat:

error[E0599]: the method `generate` exists for struct `Vec<u32>`, but its trait bounds were not satisfied
    |
154 |     pub lines:Vec<u32>,
    |         ^^^^^ method cannot be called on `Vec<u32>` due to unsatisfied trait bounds

The doc test for fltk-form it also fails for the same reasons.

I may have to give up on this idea....

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 22, 2021

@MoAlyousef I am trying to impl FltkForm manually for MyStruct<f64> since that would be FLTK's main primitive numeric value.
I tried to mimic your code in Vec to make a group:

   fn generate(&self) -> Box<dyn WidgetExt> {
        let mut g = group::Pack::default();
          g.set_spacing(5);
          //repeat numerous times for "things"
          let mut  thing = self.thing.generate();
          thing.set_align(enums::Align::Left);
          thing.set_size(w.w(), 30);
        g.end();
        Box::new(g)
    }

Using:

fn main () {
    use std::fs;
    use std::fs::File;
    use std::io::BufReader;
    use std::io::prelude::*;
    let app = app::App::default().with_scheme(app::Scheme::Gtk);
    app::set_background_color(222, 222, 222);
    let mut win = window::Window::default().with_size(400, 300);
    // substitute Things for real struct name
    let my_struct = Things::<f64>::default();
    let mut grp = group::Scroll::default()
        .with_size(300, 200)
        .center_of_parent();
    // inside group
    let form = my_struct.generate();
    grp.end();
    let mut btn = button::Button::default()
        .with_label("print")
        .with_size(80, 30)
        .below_of(&grp, 5)
        .center_x(&grp);
    win.end();
    win.show();
    while app.wait() {
       win.redraw();
    }
}

But this does not function properly as it does with manual items. What am I missing to close this issue?

@MoAlyousef
Copy link
Collaborator

MoAlyousef commented Nov 22, 2021

The macro expands in cases of structs roughly like this:

#[derive(Debug, Clone)]
pub struct MyStruct<T> {
    a: T,
    b: f64,
    c: String,
}

impl<T: Copy + Default + FltkForm> MyStruct<T> {
    pub fn default() -> Self {
        Self {
            a: T::default(),
            b: 3.0,
            c: String::new(),
        }
    }
}

impl<T: Copy + Default + FltkForm> FltkForm for MyStruct<T> {
    fn generate(&self) -> Box<dyn WidgetExt> {
        let mut p = group::Pack::default()
            .with_label(&format!("{}", "MyStruct"))
            .with_align(fltk::enums::Align::Left | fltk::enums::Align::Top);
        p.set_spacing(5);
        let mut i = self.a.generate();
        if unsafe { !i.raw_user_data().is_null() } {
            i.set_align(fltk::enums::Align::Left);
            i.set_label("a");
        }
        let mut i = self.b.generate();
        if unsafe { !i.raw_user_data().is_null() } {
            i.set_align(fltk::enums::Align::Left);
            i.set_label("b");
        }
        let mut i = self.c.generate();
        if unsafe { !i.raw_user_data().is_null() } {
            i.set_align(fltk::enums::Align::Left);
            i.set_label("c");
        }
        p.end();
        let parent = p.parent().unwrap();
        p.resize(
            parent.x() + (parent.width()/2), parent.y() + parent.h() / 9, parent.width() / 3, (3 * 30 + 5 * 3) as i32
        );
        p.auto_layout();
        Box::new(p)
    }
    fn view(&self) -> Box<dyn WidgetExt> {
        let mut p = group::Pack::default()
        .with_label(&format!("{}", "MyStruct"))
        .with_align(fltk::enums::Align::Left | fltk::enums::Align::Top);
        p.set_spacing(5);
        let mut i = self.a.view();
        if unsafe { !i.raw_user_data().is_null() } {
            i.set_align(fltk::enums::Align::Left);
            i.set_label("a");
        }
        let mut i = self.b.view();
        if unsafe { !i.raw_user_data().is_null() } {
            i.set_align(fltk::enums::Align::Left);
            i.set_label("b");
        }
        let mut i = self.c.view();
        if unsafe { !i.raw_user_data().is_null() } {
            i.set_align(fltk::enums::Align::Left);
            i.set_label("c");
        }
        p.end();
        let parent = p.parent().unwrap();
        p.resize(
            parent.x() + (parent.width()/2), parent.y() + parent.h() / 9, parent.width() / 3, (3 * 30 + 5 * 3) as i32
        );
        p.auto_layout();
        Box::new(p)
    }
}

fn main() {
    let my_struct = MyStruct::<f64>::default(); // <-- instantiate your struct

    let a = app::App::default().with_scheme(app::Scheme::Gtk);
    app::set_background_color(222, 222, 222);

    let mut win = window::Window::default().with_size(400, 300);
    let mut grp = group::Group::default()
        .with_size(300, 200)
        .center_of_parent();

    let mut form = my_struct.generate(); // <-- generate the form
    form.resize(form.x() - 50, form.y(), form.w() + 30, form.h());

    grp.end();
    grp.set_frame(enums::FrameType::EngravedFrame);
    let mut btn = button::Button::default()
        .with_label("print")
        .with_size(80, 30)
        .below_of(&grp, 5)
        .center_x(&grp);
    win.end();
    win.show();

    let v = form.get_prop("b"); // <-- get a single property
    assert_eq!(v, Some("3.0".to_owned()));

    btn.set_callback(move |_| {
        println!("{:?}", form.get_props()); // <-- get a HashMap of the properties
    });

    a.run().unwrap();
}

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 23, 2021

So does this mean I have to add FltkForm as a constraint for T everywhere? Is there a way to add things to a Generic type based on features, perhaps using cfg_attr like when using derive? I sure did find the most edge use case possible 😃
Thanks for being so thorough in your answer, I see that the limitations are very real and I may have been too optimistic about what was possible to do, unless I can change T based on features, is that something possible? I don't think it is myself, but I am still learning.

@MoAlyousef
Copy link
Collaborator

You only need to constrain T to FltkForm if you want to call generate on it, otherwise rustc will error out saying no method generate was found for T. You can use features to define types with different members, generics etc, you’ll have to duplicate them however under cfg(feature) annotations. Rust’s generics have limitations with macros unfortunately. Like fltk::widget_extends! macro can’t be used with generic structs.

@1sra3l
Copy link
Contributor Author

1sra3l commented Nov 24, 2021

Due to all the limitations imposed by using FLTK in Rust wrappers + the limitations of generics + the limitations of macros = Not possible to do exactly what I was trying to.
So on one hand the answer is still technically yes, though I am not sure if it would work for my generic.
I am going to simply use the library in the least fringe use case in rpgstat. I will just have specific FLTK only versions.
I am working on it currently over on a test project creature_math_fighter

I think I will close this issue, as it is documented well here for anyone who wants to attempt something similar.

@1sra3l 1sra3l closed this as completed Nov 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants