Issues getting started with GUI development

I’m looking at the layout intro, and I see references to MakeStringView() and MakeMenuBar(). Are these just placeholders for something else, like from incomplete docs, or are they actual functions? If they’re actual functions, I (and the compiler) can’t find them.

Also, some of the docs are not clear. The prior link mentions BGridLayout and BGridView, but this isn’t listed on the list of files and classes in the InterfaceKit. Luckily the compiler seems to be able to find GridLayout.h and GridView.h.

I’m familiar with C, C++ but totally new to development with the GUI API. I wish the pages which describe the classes, like BButton, would indicate which header file has the class. It would help when you find the page from an internet search (vs having clicked though other links in the documenation).

1 Like

Yes, they’re placeholders representing some code in your app, they’re not provided by the API.

I don’t think those classes have been documented yet.

Is there an IDE with code completion which works on Haiku with the API? I’ve been using CLion, but the code completion seems not to work.

I wish there were some good beginner examples for how to use some of the layout classes. The code I’ve seen so far seems to either be overly trivial or complex spaghetti code.

I’ve recently discovered Wordle, and have been wanting to learn the API. So I figured a Haiku Wordle could would be a great to learn! I don’t mind being the one to fight through the API to make such code. Right now, I’m creating some StringView objects which are visible if added to the window directly, but not if added to a layout. Not sure why.

1 Like

Qt Creator, Emacs, probably VIM. In the case of Qt Creator and Emacs it is helpful to be using cmake as the build tool, otherwise it can be a bit of a pain to generate the compile_commands.json file that the editors read.

Yeah, I think I had to learn my way around mainly by reading the examples that were spread out all over the place. If you want more examples, I did some conversions of old BeOS apps a long time ago, like BurnItNow and some others. Beezer is also ~70% moved over to the LayoutKit and does make use of grid layouts in the preference views.

And if you’re using Emacs, the built-in CEDET and Semantic should work fairly well too, but it doesn’t support some recentish C++ features.

Just put this in your init file after loading Semantic:

(semantic-add-system-include “/system/develop/headers” 'c-mode)
(semantic-add-system-include “/system/develop/headers/be/” 'c+±mode)

In general, any class that starts with “B” has the header file of the same name without the B. Like “BStringView” is in “StringView.h” so then BButton is in Button.h.

2 Likes

I’m trying to make a grid of BStringView objects which I’ll later change contents and colour based on user’s guesses of a word.

  • How do I make the strings closer together? Changing SetInsets(0,0) hasn’t helped, nor has making the font size smaller.
  • How can I have a box around the strings? Ideally, I’d like to have a decorative box around the string which is slightly darker than the background, and change the text colour itself.
  • General Haiku coding, what’s the best way to use layouts? When I tried the BLayoutBuilder, I felt I had to manually add all BStringView objects instead of being able to use a loop.
  • Eventually, I want to use a custom class which inherits the BStringView object so that I can use BMessage to have each string change it’s own colour. Does that sound like a sane and “Haiku way” of doing this?

While there are a lot of examples of code, most (to me) seem hard to follow. So I was hoping to teach myself and make clean, easy to follow, well-documented code in the progress to help others as well.

The grid layout is expanding to fill the window which stretches the rows and columns. You need to add some glue (flexible free space) to the side and bottom. There are a couple of ways of doing this. I usually cheat and add the grid to a normal(not grid) layout group so that I can just call AddGlue() after adding it. You’d need to wrap it in two groups to do it this way, one horizontal and one vertical.

I use builders in loops quite often. I know you like my crappy examples :stuck_out_tongue_winking_eye: , so here’s one from my DeskbarWeather app which uses a loop to add forecast days to the window.

There are probably several ways of doing this. You may be able to just subclass BButton and override the Draw() method.

The nice thing about grid layouts is that you can set the column widths and heights, like:

grid->SetMaxColumnWidth(col, StringWidth("MAX WIDTH"));
grid->SetMinColumnWidth(col, 10);

I would avoid using ResizeTo within a layout as it might do weird things.

With regards to boxes, you can make a BBox for each cell and put the BStringView inside of it. If you made a custom class as you mentioned, you can subclass BBox and add the BStringView inside of it, something like:

MyStringView::MyStringView(BString str)
     : BBox("myStringView", B_WILL_DRAW | B_FRAME_EVENTS | B_FANCY_BORDER)
{
     fInnerStringView = new BStringView("innerString", str);

     BGroupLayout* myLayout = BLayoutBuilder::Group<>(this, B_VERTICAL)
          .Add(fInnerStringView);
}

Insets, btw, are as the name implies, insetting the content within its container. Like in this example the string might be touching/overlapping the box border so you probably want to add insets to move the content away from the border.

oh, and with respect to adding items in a loop, the LayoutBuilder syntax is, afaik, meant to provide a clean visual outline of the layout. Since you’re building it dynamically it’s fine to add in a loop - but you can also add to a LayoutBuilder after it’s created via AddView for example.

1 Like

In haikudepot, there’s an application called layout Editor, that works with the built in layout model, stack tile stuff etc I’m not sire of it’s current condition but it did work, and would show the code in a adjacent window

Are you talking about ALE?

You have specified a window size, and then filled your window with some widgets. The layout kit then expands the widgets to fill all the available space.

You can do several different things:

  • Add the B_AUTO_UPDATE_SIZE_LIMITS flags (next to B_NOT_ZOOMABLE) to have the window resize itself automatically and be as small as possible to fit the content
  • Add some “glue” or some other widgets to use the extra space. You can set the weight of each line column, for example set all the existing columns with a weight of 0 (so they will be as small as possible) and the extra one with a non-zero weight, so it will take up all the extra space.

You can set the string view colors using SetHighColor and SetLowColor.
Then you can set your grid layout to have a little spacing between the widgets (using SetSpacing()), so the root view background color will show through.

It depends what you’re doing. The layout builder is great for a declarative-style creation of a window. But if you want to add a lot of widgets in a loop, it isn’t helpful (a loop isn’t declarative).

So in that case you can add things directly to your layout, without using a builder:

grid->AddView(new BStringView("xxx", "yyy", x, y));

I like your DeskbarWeather app @Lrrr, and I see how you used the LayoutBuilder in a loop, but that’s not what I was thinking. Your example has the layout being entirely defined in one iteration of the loop. I was hoping for something where the layout if defined before the loop, items added inside, and call End() after the loop finishes. It seems that each function does not keep state for the entirety of the builder, rather instead returning an object which is built, or the parent object. So that lead me to this:

    BLayoutBuilder::Group<> horizontalBuilder = BLayoutBuilder::Group<>(horizontalView);
    
    horizontalBuilder.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
    	.AddGlue();
    
    BLayoutBuilder::Group::GridBuilder gridBuilder =  horizontalBuilder.AddGrid(B_USE_HALF_ITEM_SPACING, 0.0);
    
     for(int col = 1; col	<= 5; col++) {
    	for(int row = 1; row <= 6; row++) {
    		BString name;
    		name << "r" << row << "c" << col;
    		BStringView *thisView = new BStringView(name.String(), name.String());
    		thisView->SetFont(&font);
    		thisView->SetAlignment(B_ALIGN_LEFT);
    		BBox *box = new BBox(B_FANCY_BORDER, thisView);
    		horizontalBuilder.Add(box);
            //gridBuilder.Add(box, col, row); // compiler doesn't like the int args here
    	}
    }
    
    horizontalBuilder
    	.End() // End AddGrid
    	.AddGlue().
    	End(); // End AddGroup

I noticed that I have to use the BLayoutBuilder::Group::GridBuilder type, but it seems like maybe that’s not possible, as I get this:

MainWindow.cpp:28:40: error: qualified-id in declaration before 'gridBuilder'
   28 |     BLayoutBuilder::Group::GridBuilder gridBuilder =  horizontalBuilder.AddGrid(B_USE_HALF_ITEM_SPACING, 0.0);
      |                                        ^~~~~~~~~~~
MainWindow.cpp:44:10: error: request for member 'AddGlue' in 'horizontalBuilder.BLayoutBuilder::Group<>::<anonymous>.BLayoutBuilder::Base<void*>::End()', which is of non-class type 'void*'
   44 |         .AddGlue().

And it doesn’t like AddGlue(). So what is the best way to do this?

I was able to make it work by using the auto keyword. I don’t like doing that on principle, since it hides information to the developer, and because I’d like to understand what’s going on.

    
    horizontalBuilder.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
    	.AddGlue();
    
    //BLayoutBuilder::Group::GridBuilder gridBuilder =  horizontalBuilder.AddGrid(B_USE_HALF_ITEM_SPACING, 0.0);
    auto gridBuilder =  horizontalBuilder.AddGrid(B_USE_HALF_ITEM_SPACING, 0.0);
    
     for(int col = 1; col	<= 5; col++) {
    	for(int row = 1; row <= 6; row++) {
    		BString name;
    		name << "r" << row << "c" << col;
    		BStringView *thisView = new BStringView(name.String(), name.String());
    		thisView->SetFont(&font);
    		thisView->SetAlignment(B_ALIGN_LEFT);
    		BBox *box = new BBox(B_FANCY_BORDER, thisView);
    		//horizontalBuilder.Add(box);
    		gridBuilder.Add(box, col, row);
    	}
    }
    
    gridBuilder
    	.End() // End AddGrid
    	.AddGlue().
    	End(); // End AddGroup

screenshot2

Progress!

1 Like

I think you just need the template specialization <> after Group

BLayoutBuilder::Group<>::GridBuilder gridBuilder =  horizontalBuilder.AddGrid(B_USE_HALF_ITEM_SPACING, 0.0);

Here is what I usually do in that situation:

    BGridLayout* innerLayout;

    BLayoutBuilder::Group<>(rootView)
        .Add(...) // add whatever you need to your layout
        .AddGrid()
            .GetLayout(&innerLayout) // get a pointer to this specific grid, we will populate it later in the loop
        .End()
        .Add(...) // more items as needed
    .End();

     for(int col = 1; col	<= 5; col++) {
    	for(int row = 1; row <= 6; row++) {
    		BString name;
    		name << "r" << row << "c" << col;
    		BStringView *thisView = new BStringView(name.String(), name.String());
    		thisView->SetFont(&font);
    		thisView->SetAlignment(B_ALIGN_LEFT);
    		BBox *box = new BBox(B_FANCY_BORDER, thisView);
    		innerLayout->AddView(box, col, row);
    	}
    }
1 Like

Are these the same data types?

No.

There is a layout class that you can use directly (BGridLayout, BGroupLayout, etc). This is the class actually responsible for doing the layout.

The “layout builder” classes are just a wrapper above it that provides the tree-like structure with .Add() and .End() and allows to easily build a more complex layout with nested grids, groups, etc easily. It will create several layout class instances as needed (whenever you call AddGrid, a new BGridLayout is created, anlongside with a temporary GridBuilder that will populate it)

1 Like