Custom containers

While the existing stock containers (panel, tab, accordion, etc.) should suffice for most apps, there are times when you need to create a custom container that can hold and manage other controls.

This can be done using a special macro: #[CustomContainer(...)] as follows:

#[CustomContainer(...)]
struct MyCustomContainer {
    // additional fields
}

A custom container accepts the same attributes as custom controls (via #[CustomContainer(...)] macro):

  • events with two possible values or combinations: MenuEvents and/or CommandBarEvents:
    #[CustomContainer(events = MenuEvent+CommandBarEvent)]
    struct MyCustomContainer {
        // additional fields
    }
    
  • overwrite to allow one to overwrite certain traits (for painting or resizing):
    #[CustomContainer(overwrite = OnPaint+OnResize)]
    struct MyCustomContainer {
        // additional fields
    }
    
  • emit to describe a list of events that the current container can emit towards the event loop:
    #[CustomContainer(emit = DataChanged+ItemSelected+ContainerResized)]
    struct MyCustomContainer {
        // additional fields
    }
    
  • commands (as they are described in Commands section)

Difference between Custom Controls and Custom Containers

While both #[CustomControl] and #[CustomContainer] generate very similar code, they serve different conceptual purposes:

  • Custom Controls are typically leaf elements in the UI hierarchy - they represent individual interactive or display elements (buttons, labels, input fields, etc.)
  • Custom Containers are designed to hold and manage other controls - they act as layout managers or grouping elements that can contain child controls

When creating a custom container, two aditional methods are available:

MethodDescription
add(...)Adds a child control to the container (much like the methods from a panel or window controls)
set_margins(...)Sets the margins of the container. The margins are the space between the container and its child controls. By default they are 0 and are meaningless for a regular control that has no children.

A simple container example

The following example creates a simple custom container that acts as a bordered panel with a title bar and can hold other controls:

use appcui::prelude::*;

#[CustomContainer(overwrite = OnPaint)]
struct TitledContainer {
    title: String,
}

impl TitledContainer {
    fn new(layout: Layout, title: &str) -> Self {
        let mut me = Self {
            base: ContainerBase::new(layout, true),
            title: title.to_string(),
        };
        me.set_margins(1, 1, 1, 1);
        me
    }
}

impl OnPaint for TitledContainer {
    fn on_paint(&self, surface: &mut Surface, _theme: &Theme) {
        let size = self.size();
        surface.draw_rect(
            Rect::with_point_and_size(Point::ORIGIN, size), 
            LineType::AsciiRound, 
            charattr!("yellow"));
        surface.fill_horizontal_line(2, 
                                     0, 
                                     size.width as i32 - 3, 
                                     ~~Character~~::with_attributes(' ', charattr!("black,yellow")));

        let format = TextFormatBuilder::new()
                                        .position(size.width as i32/2, 0)
                                        .align(TextAlignment::Center)
                                        .attribute(charattr!("black,yellow"))
                                        .build();
        surface.write_text(&self.title, &format);
    }
}

fn main() -> Result<(), appcui::system::Error> {
    let mut a = App::new().build()?;
    let mut w = window!("caption:'Container Example',a:c,w:50,h:20");

    // Create the custom container
    let mut container = TitledContainer::new(layout!("l:2,t:2,r:2,b:2"), "My Custom Container");

    // Add child controls to the container
    container.add(label!("'Hello from inside!', x:2, y:2, w:20"));
    container.add(button!("'Click Me', x:2, y:4, w:12"));

    w.add(container);
    a.add_window(w);
    a.run();
    Ok(())
}