Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 additional 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(())
}