Storing and passing an fltk.rs window in a struct

I've been experimenting with passing an FLTK generated widow around between functions. I suspect that what I'm trying to do is one of those things that just won't work. Still, if I can get it working, it will solve some problems for me. I have two code snippets. The first one works, the second one doesn't. Here's the first:

use fltk::app::set_font_size;
use fltk::enums::Color;
use fltk::prelude::{GroupExt, WidgetExt};
use fltk::window::Window;

fn main() {
    let app = fltk::app::App::default();
    let mut win = makeawin();

    win.end();
    win.show();
    app.run().unwrap();
}

fn makeawin() -> Window {
    let mut dwin = Window::default().with_size(825, 900).with_pos(1100, 200);
    set_font_size(20);
    dwin.set_color(Color::Cyan);
    dwin.set_label("Question Bank Display");
    dwin.make_resizable(true);

    dwin
}

Like I said, this works just fine, drawing a pretty, cyan-colored window on my screen. I had the (maybe not so smart) idea that it would be handy to create a struct to hold that window (along with whatever other widgets I want to add to my window) and then pass that struct back and forth between functions as needed. So, here's what I came up with (that doesn't work):

use fltk::app::set_font_size;
use fltk::enums::Color;
use fltk::prelude::{GroupExt, WidgetExt};
use fltk::window::Window;

struct Wndw {
    win: Window,
}

impl Wndw {
    fn new() -> Self {
        Self {
            win: Window::default().with_size(300, 400).with_pos(500, 200),
        }
    }
}

fn main() {
    let app = fltk::app::App::default();
    let mut winstruct = Wndw::new();

    makeawin(&mut winstruct);

    winstruct.win.end();
    winstruct.win.show();

    app.run().unwrap();
}

fn makeawin(winstruct: &mut Wndw) {
    let mut dwin = Window::default().with_size(825, 900).with_pos(1100, 200);
    set_font_size(20);
    dwin.set_color(Color::Cyan);
    dwin.set_label("Question Bank Display");
    dwin.make_resizable(true);

    winstruct.win = dwin.clone();
}

This compiles and runs without errors, but the window does not display. It wouldn't surprise me if the problem is one of those simple things that I should be smart enough to catch, so please be patient with me. :slightly_smiling_face:

Any thoughts?

I think the issue lies in how FLTK manages children. It might be that FLTK considers dwin a child of win (since you don’t call end on it) then doesn’t know how to handle things (calls show on the child and not the parent, since the clone is a pointer copy). In your function, try to avoid creating a new window, instead call resize on the passed win:

fn makeawin(winstruct: &mut Wndw) {
    winstruct.win.resize(1100, 200, 825, 900);
    set_font_size(20);
    winstruct.win.set_color(Color::Cyan);
    winstruct.win.set_label("Question Bank Display");
    winstruct.win.make_resizable(true);
}

To make the initial code work, you would have to call end() on the window:

use fltk::app::set_font_size;
use fltk::enums::Color;
use fltk::prelude::{GroupExt, WidgetExt};
use fltk::window::Window;

struct Wndw {
    win: Window,
}

impl Wndw {
    fn new() -> Self {
        let win = Window::default().with_size(300, 400).with_pos(500, 200);
        win.end();
        Self {
            win
        }
    }
}

fn main() {
    let app = fltk::app::App::default();
    let mut winstruct = Wndw::new();

    makeawin(&mut winstruct);

    winstruct.win.end();
    winstruct.win.show();

    app.run().unwrap();
}

fn makeawin(winstruct: &mut Wndw) {
    let mut dwin = Window::default().with_size(825, 900).with_pos(1100, 200);
    set_font_size(20);
    dwin.set_color(Color::Cyan);
    dwin.set_label("Question Bank Display");
    dwin.make_resizable(true);

    winstruct.win = dwin.clone();
}
2 Likes

Thanks for getting back to me. I haven't yet had a chance to experiment with this, but I wanted to ask you a question before your Saudi workday ends. If I call end() in the new() function, won't that prevent me from adding other widgets to that window? The idea is to eventually add a scrollbar and some text display boxes to the window and store them in the struct. Then, when I need to modify the window content, all I have to do is pass the struct and the window along with all its child widgets will be there for me to access. Will calling end() in the new() function preclude doing that?

Here's my logic and it's likely that I don't correctly perceive how child widgets become attached to windows. My current understanding is that any widget created between the win = new() call and win.end() becomes a child. If I call win.end() in new(), then there won't be any opportunity to add any other widgets. Please go ahead and correct my thinking.

I tried to find out from the API docs and found this.

https://docs.rs/fltk/latest/fltk/prelude/trait.GroupExt.html

In the above example, the button btn will be parented by the window. After ending such GroupExt widgets, any other widgets instantiated after the end call, will be instantiated outside. These can still be added using the ::add(&other_widget) method (or using ::insert):

2 Likes

That might solve the problem. Thanks. I'll keep you posted.

1 Like

Ok, so I'm starting to play around with the suggestions above, and it brings up something that reveals just how much I'm a Rust amateur. Look at this code snippet from MoAlyousef:

impl Wndw {
    fn new() -> Self {
        let win = Window::default().with_size(300, 400).with_pos(500, 200);
        win.end();
        Self {
            win
        }
    }
}

I'm going to use this code, but I have two questions about it. First, the keyword Self and the way we use it when creating methods in an impl block has always confused me. I use it because that's what seems to work and all the examples use it and our much-loved Rust book says to use it, but the logic of its purpose/usage still escapes me.

Second, in this snippet, why is the function return contained in its own, little Self scope?

        Self {
            win
        }

I don't think MoAlyousef would have put it there without a good reason. So, if someone could enlighten me concerning these two questions I would love it. Thanks!

Self is an alias for the type of your Wndw struct because this is all inside the impl Wndw block. So that last statement in the new method is just creating a Wndw struct and setting its win field to the local variable win. This could have been written Wndw { win: win } instead, with the same effect.

The reason that win: win is abbreviated as win is to make it more concise, of course. When a local variable name is the same as a field name, Rust allows abbreviating it this way.

The reason that Self is used (normally) instead of Wndw is that you may have an impl block with a much more complex type, such as impl Wnd<Xxx<Yyy<...>>>. In that case Self represents that entire type, so it is more concise. So we get in the habit of always using the Self alias to create the struct.

3 Likes

I implemented your suggestions and it is now working fine. I still have some more experimentation to do with this technique, but I think it's going to make things run much smoother when I implement it in my main project.

Here's the working code so far:

use fltk::app::set_font_size;
use fltk::enums::Color;
use fltk::prelude::{DisplayExt, GroupExt, WidgetBase, WidgetExt};
use fltk::text;
use fltk::text::{TextBuffer, TextDisplay};
use fltk::window::Window;

// region Structs
struct Wndw {
    win: Window,
    textboxes: Vec<TextDisplay>,
}

impl Wndw {
    fn new() -> Self {
        let win = Window::default().with_size(300, 400).with_pos(500, 200);
        win.end();

        Self {
            win,
            textboxes: Vec::new(),
        }
    }
}
// endregion


fn main() {
    let app = fltk::app::App::default();
    let mut winstruct = Wndw::new();
    makeawin(&mut winstruct);

    // Fill the textbox vector.

    let mut yyy = 51;
    let mut i = 0;
    while i < 6 {
        add_textbox(&mut winstruct, yyy);
        i += 1;
        yyy += 150;
    }

    // Add the textboxes to the window.
    for item in winstruct.textboxes.iter_mut()
    {
        winstruct.win.add(item);
    }

    winstruct.win.end();
    winstruct.win.show();

    app.run().unwrap();
}

fn makeawin(winstruct: &mut Wndw) {
    let mut dwin = Window::default().with_size(825, 900).with_pos(1100, 200);
    set_font_size(20);
    dwin.set_color(Color::Cyan);
    dwin.set_label("Question Bank Display");
    dwin.make_resizable(true);

    winstruct.win = dwin;
}

fn add_textbox(winstruct: &mut Wndw, yyy: i32) {
    let mut txtbuff1 = TextBuffer::default();
    txtbuff1.set_text("Text1 Text1 Text1");

    let mut textbox = TextDisplay::new(0, yyy, 825, 150, "");
    textbox.set_buffer(txtbuff1);
    textbox.wrap_mode(text::WrapMode::AtBounds, 0);
    textbox.set_color(fltk::enums::Color::White);
    textbox.set_text_size(22);
    textbox.set_text_color(fltk::enums::Color::Black);

    winstruct.textboxes.push(textbox);

}

Thanks for all your help, everyone! :grinning: :nerd_face: :older_man:

1 Like

Well, I thought I was going to be able to leave you guys alone, but it hasn't worked out that way. Using the same techniques we came up with in the above code, I added a scrollbar to the window and everything works fine. Of course, this is all following closely to what I need to do in my main project, so I decided to add a title to the top of the window. And, to make things more complicated, I decided to make that title box editable, rather than just using a frame with text added. I've used FLTK's TextEditor struct before and am fairly comfortable with it. However, when I add it to the code, using the techniques we've developed here, it just doesn't show. The code runs fine with a nice column of TextDisplay boxes that scroll very nicely, but the title box doesn't show. My IDE's AI is also confused, giving me all kinds of not so useful advice. The current iteration of the code is below. Any thoughts?

use fltk::app::set_font_size;
use fltk::enums::Color;
use fltk::group::Scroll;
use fltk::prelude::{DisplayExt, GroupExt, WidgetBase, WidgetExt};
use fltk::text;
use fltk::text::{TextBuffer, TextDisplay, TextEditor};
use fltk::window::Window;

// region Structs
struct Wndw {
    win: Window,
    title_editor: TextEditor,
    scroll: Scroll,
    textboxes: Vec<TextDisplay>,
}

impl Wndw {
    fn new() -> Self {
        let win = Window::default().with_size(300, 400).with_pos(500, 200);
        win.end();
        let scroll = Scroll::default();
        scroll.end();

        Self {
            win,
            title_editor: TextEditor::default(),
            scroll,
            textboxes: Vec::new(),
        }
    }
}
// endregion

fn main() {
    let app = fltk::app::App::default();
    let mut winstruct = Wndw::new();

    makeawin(&mut winstruct);

// region Add a title bar using an editable TextEditor box.
    makeatexteditor(&mut winstruct);
    winstruct.win.add(&winstruct.title_editor);
// endregion

// region Add a scrollbar to the window.
    makeascrollbar(&mut winstruct);
    winstruct.win.add(&winstruct.scroll);
// endregion

// region Make and add textboxes.

            // Fill the textbox vector.
    let mut yyy = 51;
    let mut i = 0;
    while i < 10 {
        makeatextbox(&mut winstruct, yyy);
        i += 1;
        yyy += 150;
    }

            // Add the textboxes to the window.
    for item in winstruct.textboxes.iter_mut()
    {
        winstruct.scroll.add(item);
    }
// endregion

    winstruct.win.end();
    winstruct.win.show();

    app.run().unwrap();
}

fn makeawin(winstruct: &mut Wndw) {
    let mut dwin = Window::default().with_size(825, 900).with_pos(1100, 200);
    set_font_size(20);
    dwin.set_color(Color::Cyan);
    dwin.set_label("Question Bank Display");
    dwin.make_resizable(true);

    winstruct.win = dwin;
}

fn makeatextbox(winstruct: &mut Wndw, yyy: i32) {
    let mut txtbuff1 = TextBuffer::default();
    txtbuff1.set_text("Text1 Text1 Text1");

    let mut textbox = TextDisplay::new(0, yyy, winstruct.win.width(), 150, "");
    textbox.set_buffer(txtbuff1);
    textbox.wrap_mode(text::WrapMode::AtBounds, 0);
    textbox.set_color(fltk::enums::Color::White);
    textbox.set_text_size(22);
    textbox.set_text_color(fltk::enums::Color::Black);

    winstruct.textboxes.push(textbox);
}

fn makeascrollbar(winstruct: &mut Wndw) {

    // Create scroll group
    let mut scroll = Scroll::new(0, 51, winstruct.win.width(), 900, "");
    scroll.set_scrollbar_size(15);

    // Add scroll to the Wndw struct.
    winstruct.scroll = scroll;
}

fn makeatexteditor(winstruct: &mut Wndw) {
    let mut texteditor = TextEditor::new(0, 0, winstruct.win.width(), 50, "Default Title");
    texteditor.set_text_size(22);
    texteditor.set_text_color(fltk::enums::Color::Black);
    texteditor.set_color(fltk::enums::Color::Blue);

    winstruct.title_editor = texteditor;

    // todo: It would be nice to center the text, but that is really
    //      difficult to do, so leave for later.
}

Well, I'm going to answer my own question. I had neglected to create a text buffer and add it to the editor window. Once I got that in place, the title editor window showed up just fine. Here's the corrected makeatexteditor() function:

fn makeatexteditor(winstruct: &mut Wndw) {
    let mut buf = TextBuffer::default();
    buf.set_text("This Is A Title");

    let mut ted = TextEditor::new(0, 0, winstruct.win.width(), 50, "");
    ted.set_text_size(22);
    ted.set_text_color(fltk::enums::Color::Black);
    ted.set_color(fltk::enums::Color::Blue);
    ted.set_buffer(buf.clone());   // Clone is used here to avoid an ownership error.

    winstruct.title_editor = ted;

    // todo: It would be nice to center the text, but that is really
    //      difficult to do, so leave for later.
}

I have yet to allow for pulling any edits out of that window and will likely leave that for when I incorporate all this into my main project.

1 Like