You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+82-24Lines changed: 82 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -542,41 +542,99 @@ Note: Image display is a feature introduced experimentally in a fork of libui-ng
542
542
543
543
## Memory Management Policy
544
544
545
-
libui-ng is a C library, so normally the user is expected to manage memory manually.
546
-
However, it also provides some automatic memory management. The basic rule is: when a parent control is destroyed, all of its child controls are automatically destroyed as well.
545
+
UIng wraps libui-ng, a C library with explicit ownership rules. Crystal's GC keeps wrapper objects alive, but it does not decide when native widgets are destroyed. Use `destroy` for controls and `free` for standalone native resources.
547
546
548
-
For example, when destroy is called on controls such as Window, Box, Grid, Group, or Tab, it first destroys all child controls and then destroys itself.
547
+
The main rule is simple: a parent control owns its children. Destroying a parent destroys the native children too, and UIng marks the child wrappers as destroyed. Do not call `destroy` on a child while it still belongs to a parent.
549
548
550
-
In the case of a Window, there are two common situations:
549
+
```crystal
550
+
window = UIng::Window.new("App", 400, 300)
551
+
box = UIng::Box.new(:vertical)
552
+
button = UIng::Button.new("OK")
553
+
554
+
box.append(button)
555
+
window.child = box
556
+
557
+
window.destroy # also destroys box and button
558
+
```
559
+
560
+
If you want to reuse a child, detach it first when the container supports it:
561
+
562
+
```crystal
563
+
box.delete(button) # detaches; button is still alive
564
+
other_box.append(button)
565
+
```
551
566
552
-
When the user clicks the close (×) button on the title bar
553
-
In this case, `on_closing` is triggered. If the callback returns `true`, libui-ng destroys the native window. UIng mirrors that native destruction by marking the `Window` wrapper and all child wrappers as destroyed. Do not call `Window#destroy` from inside `on_closing`; return `true` to allow the close, or `false` to keep the window open.
567
+
Container behavior:
554
568
555
-
When on_should_quit is triggered
556
-
This means the entire program is about to quit. But here, Window#destroy is not called automatically, so the user needs to call it explicitly.
569
+
- `Window`and `Group` have one child. Assigning a new child detaches the old child without destroying it. Destroying the `Window` or `Group` destroys its current child.
570
+
- `Box`, `Form`, and `Tab` support `delete(index)` and `delete(child)`. `delete` detaches the child; call `child.destroy` afterwards if you are done with it.
571
+
- `Grid`does not currently expose a delete API. Destroy the whole grid, or build a new grid if you need to remove children.
572
+
- Controls with no parent can be destroyed directly with `destroy`.
557
573
558
-
Crystal has garbage collection (GC) and provides a finalize hook when objects are freed. However, GC timing is non-deterministic, and libui-ng controls often require deterministic destruction order.
574
+
Window closing has one special rule. In `Window#on_closing`, return `true` to let libui-ng destroy the native window, or `false` to keep it open. Do not call `window.destroy` inside that callback.
575
+
576
+
```crystal
577
+
window.on_closing do
578
+
UIng.quit
579
+
true
580
+
end
581
+
```
559
582
560
-
Therefore, UIng's primary policy is:
583
+
`on_should_quit`does not destroy windows for you. If your app exits from there, explicitly destroy any windows you created.
561
584
562
-
- Control objects (`Window`, `Box`, `Grid`, `Tab`, etc.) are managed with explicit `destroy`.
563
-
- Child controls are owned by their parent. To remove a child without destroying it, use the container's delete/remove API where available. Destroying a child directly while it still has a parent is not allowed.
564
-
- C resources that require strict ordering (for example `Table::Model`) are managed with explicit `free`.
565
-
- `finalize`is never treated as a primary cleanup mechanism for order-sensitive resources.
585
+
Some non-control resources must be freed manually:
566
586
567
-
Some lightweight wrappers include idempotent cleanup in `finalize` as a fallback, but this is best-effort only and not a correctness guarantee.
587
+
- `Table::Model`: destroy all `Table` controls using the model first, then call `model.free`.
588
+
- `Image`: call `image.free` when it is no longer needed. `ImageView#image=` copies or retains what it needs, but table image values borrow the image, so keep the `Image` alive while the table may display it.
589
+
- `Table::Selection`: selections passed to `on_selection_changed` are freed automatically after the callback. If you call `table.selection` manually, free the returned selection when done.
568
590
569
-
It may seem a bit tricky, but understanding how libui-ng handles memory will help you avoid problems when building applications.
591
+
After `destroy` or `free`, do not use the wrapper again.
570
592
571
-
Some controls require specific destruction order for proper memory cleanup
593
+
Example cleanup patterns:
572
594
573
-
- [#6](https://github.com/kojix2/uing/issues/6)
574
-
- Table
575
-
- [#19](https://github.com/kojix2/uing/issues/19)
576
-
- MultilineEntry
577
-
- Tab
578
-
- Grid
579
-
- Area
595
+
```crystal
596
+
# Reuse a child by detaching it first.
597
+
left = UIng::Box.new(:vertical)
598
+
right = UIng::Box.new(:vertical)
599
+
button = UIng::Button.new("Move me")
600
+
601
+
left.append(button)
602
+
left.delete(button) # button is detached, not destroyed
603
+
right.append(button)
604
+
```
605
+
606
+
```crystal
607
+
# Remove a child permanently.
608
+
box = UIng::Box.new(:vertical)
609
+
button = UIng::Button.new("Delete me")
610
+
611
+
box.append(button)
612
+
box.delete(button)
613
+
button.destroy
614
+
```
615
+
616
+
```crystal
617
+
# Table models must outlive tables that use them.
618
+
handler = UIng::Table::Model::Handler.new
619
+
model = UIng::Table::Model.new(handler)
620
+
table = UIng::Table.new(model)
621
+
622
+
# ... use table ...
623
+
624
+
table.destroy
625
+
model.free
626
+
```
627
+
628
+
```crystal
629
+
# Images can be freed after ImageView receives them.
0 commit comments