Runtime and Lifetime
A UIng application creates its controls between UIng.init and UIng.main.
The main loop then waits for native events and invokes the callbacks registered
by the application.
Application lifecycle
UIng.initinitializes libui-ng.window.showmakes a completed window visible.UIng.mainruns the native event loop.UIng.quitasks that loop to stop.UIng.uninitreleases application-wide resources after the loop ends.
Make sure to call UIng.uninit after UIng.main so the matching
UIng.uninit is not forgotten:
UIng.init
# Build and show the interface here.
UIng.main
UIng.uninit
UIng.init also accepts a block. In that form, UIng.uninit is called
automatically after the event loop returns:
UIng.init do
# Build and show the interface here.
UIng.main
end
The block form pairs init with uninit, including when the block raises an
exception. It does not destroy windows or other resources created inside the
block. Destroy every top-level window before leaving the block, including on
error paths, and free standalone resources according to their ownership rules
below. An exception during setup can otherwise reach uninit while a window
is still alive.
Closing a single window
Normally, you do not need to destroy controls yourself. Stop the event loop
and return true from on_closing; libui-ng destroys the Window and its
children.
window.on_closing do
UIng.quit
true
end
Do not call window.destroy in this callback.
Control ownership
Some controls contain other controls. The containing control is the parent, and an attached control is its child. Destroying a parent automatically destroys all of its children, so normally only the top-level parent needs to be destroyed.
window = UIng::Window.new("App", 400, 300)
box = UIng::Box.new(:vertical)
button = UIng::Button.new("OK")
box.append(button)
window.child = box
window.destroy # also destroys box and button
UIng marks the Crystal wrappers for those children as released. They cannot be used after their parent is destroyed.
Detach a child before reusing it elsewhere:
button.detach
other_box.append(button)
Detach a child before destroying it individually:
button.detach
button.destroy
Calling destroy on an attached child raises an exception and leaves the
child intact.
WindowandGrouphave one child. Assigningnilor a new child detaches the old child without destroying it.Box,Form,Tab, andGridsupportdelete(child);Box,Form, andTabalso supportdelete(index).- A control without a parent can be destroyed directly.
Closing a window or application
UIng.quit stops the event loop; it does not destroy windows. UIng.uninit
releases application-wide resources but does not destroy windows created by
the application. Make sure every top-level window has been destroyed before
calling UIng.uninit.
Window#on_closing handles the close button. Return true to close the
Window or false to keep it open. With multiple Windows, do not call
UIng.quit unconditionally whenever one Window closes.
UIng.on_should_quit handles application-wide requests such as a Quit menu.
Destroy every top-level Window, then return true:
UIng.on_should_quit do
window.destroy unless window.released?
true
end
For multiple Windows, repeat this for every top-level Window. released?
prevents double destruction.
Other resources
Objects that are not controls include Crystal-managed values and native
resources that need explicit cleanup. Creating one with .new or receiving it
from a method does not, by itself, determine which rule applies:
- A native resource used through
.openis freed when the block ends. - Whether a callback value can be retained depends on its type.
Table::SelectionandFontDescriptorcan be kept; borrowedTable::ValueandAttributeobjects cannot be used after the callback.
Specific resource rules are:
Table::Model: request destruction of allTablecontrols using the model before callingmodel.free. If native destruction is pending, the wrapper becomes unavailable immediately and the native model is freed after the last Table destruction completes.Image: callfreewhen it is no longer needed. It can be freed after passing it toImageView#image=or aToolbaritem, but must remain alive while a table uses it.Toolbar: an attached toolbar is freed when its window is destroyed. Detach it first to reuse it; detached toolbars still needfree.Draw::Path,Draw::TextLayout, andAttributedString: prefer.openwhere available so cleanup occurs when the block ends.Table::Selection: results fromtable.selection, callback values, and values created withTable::Selection.new(rows)are independent snapshots. They can be retained after a callback and do not needfree.FontDescriptor: results fromFontButton#fontand callback values can be retained. Crystal manages them; do not callfree.OpenTypeFeatures: Crystal manages the tags and values. It can be reused after passing it toAttribute.new_featuresand needs nofree.Attribute#featuresreturns an independent copy.TM: Crystal manages it. Outside Windows,zone=copies and retains its string. Nofreeis needed.Table::Value: a value returned fromcell_valueis managed by libui-ng. A value passed toset_cell_valueis valid only until that callback returns. Free a value you create but do not pass to libui-ng.Attribute: afterset_attribute, the receivingAttributedStringowns it. An attribute yielded by enumeration is valid only for that block.OpenTypeFeaturesmay be read recursively during enumeration, but tags cannot be added or removed.AttributedStringmay also be read recursively, but cannot be freed or structurally modified until enumeration finishes.- A draw context is valid only during its draw callback.
Calling destroy or free makes the corresponding wrapper unavailable for
further use.