What’s New¶
Significant changes since the last tagged release (v0.1.2).
Major changes¶
TreeView: dict-tree model with stable key paths¶
TreeView (and its subclass TableView) now stores tree data as
a hierarchy of dicts keyed by stable string identifiers. Paths to
nodes are arrays of those keys, so a path stays valid no matter how
the visible tree is sorted.
tree.set_tree({
"Documents": {
"report.pdf": {TYPE: "PDF", SIZE: 2400},
"notes.txt": {TYPE: "Text", SIZE: 12},
},
"Pictures": {
"photo.jpg": {TYPE: "JPEG", SIZE: 3200},
},
});
The first column auto-displays the node’s dict key when the row
supplies no value for it, so most interiors need no explicit values
at all. Mixed dicts (primitives plus nested objects) split
automatically: the primitives become the interior’s own column
values; the objects become its children. An __values__ sentinel
is available for the rare case where the split is ambiguous. See
TreeView for the full reference.
Column descriptors and types¶
Columns now carry a stable key (auto-generated as _col0,
_col1 … if not supplied), and a richer set of types:
"string" (alias "str"), "integer" (alias "int"),
"float" (alias "number"), "boolean" (renders ✓ when
truthy), and "icon". An halign field controls horizontal
alignment with sensible per-type defaults (numeric → right,
boolean / icon → center, otherwise left).
All per-column / per-row methods now take a key (not an index):
set_column_width(col_key, width),
sort_by_column(col_key, ascending),
insert_column(column, before=null), delete_column(col_key),
set_cell(path, col_key, value), etc.
Auto-spanning¶
Within a row, a column whose key is missing (or whose value is
null/undefined) is “absent” and the preceding present cell
extends across it via CSS grid spans. Explicit empty strings still
render as their own (empty) cell. This lets parent rows be terse:
{NAME: "Documents"} with the rest of the columns omitted
renders as a single cell across the row.
New tree methods¶
add_tree(tree, parent=null)– merge a dict-tree under a parent path; existing same-key children are replaced subtree-deep.update_tree(tree)– replace the tree, but preserve selection by path (previouslyset_treecleared selection unconditionally).get_subtree(status)– return a dict-tree containing"all","selected","expanded", or"collapsed"nodes (with descendants and ancestors so the result is connected).clear_selection()– explicit method to drop all selection.set_sortable(tf)– toggle click-to-sort.
Window controls (TopLevel and MDISubWindow)¶
TopLevel now supports the same window controls that
MDISubWindow has, plus a “shade” (roll up to title bar) state
on both.
New TopLevel options (all default false except
shadeable which defaults true):
minimizable– show a minimize button. Minimized windows auto-stack along the bottom of the viewport, wrapping rows when full.maximizable– show a maximize button. Maximize fills the browser viewport (snapshot at click time; doesn’t follow viewport resizes).lowerable– show a send-to-back button.shadeable– allow rolling up to just the title bar. Defaulttrue. Available from the right-click context menu and via double-click on the title bar.icon– URL ordata:URI for a title-bar icon.
New methods: set_icon(url), toggle_minimize(),
toggle_maximize(), toggle_shade(),
set_window_state(state), get_window_state().
New callback: window-state – fires with the new state name
("normal", "shaded", "minimized", "maximized").
The Python side syncs this so window state survives reconnect.
MDIWidget.add_widget accepts shadeable (default true).
Sub-windows gain toggle_shade() and the right-click context menu.
The active (topmost) sub-window’s title bar is now drawn slightly
lighter, like the active tab in a TabWidget.
Image: binary-frame protocol¶
Image.set_binary_image(format, buffer) accepts raw bytes from a
WebSocket binary frame and renders them via a Blob of the
matching MIME type ("jpeg" / "png" / "webp" / "gif").
This avoids the ~33% base64 overhead of the JSON set_image path
and is intended for streaming use cases (animation, video frames,
etc.). The Python side’s binding stores the latest frame so it is
replayed on reconnect.
Browser text-select disabled by default¶
Drag-to-highlight inside widgets is now disabled by default — it
interferes with row click/drag, shift-click range select, etc.
Form controls (TextEntry, TextArea, TextSource,
TextEntrySet, the treeview cell editor) and contenteditable
elements always allow selection regardless. Per-widget opt-in via
the new set_allow_text_selection(tf) method on the Widget
base class (or via constructor option on TreeView / TableView).
Other notable additions¶
MenuActionactivatedcallback signature simplified. Old:handler(widget, text, checked). New:handler(widget)for non-checkable actions;handler(widget, checked)for checkable ones. The label can still be queried viawidget.get_text(). This is a breaking change for any handler that took the text arg.Button.set_color(bg)now uses thebackgroundshorthand so the requested colour shows through the new sculpted gradient and:hover/:activerules. Pass an empty string to revert.New
WindowMenushared helper module powers both context menus.Widgetbase class now adds apgwidgets-widgetclass to every widget element and exposesset_allow_text_selection.Restyled widgets: sans-serif default, sculpted buttons, distinct slider/dial thumbs, etc.
ScrollAreaandAbstractScrollAreaimprovements; newset_thumb_percentAPI and shared scrollbar code.Box/GridBoxlayout fixes: rigidstretch=0children, cells that re-flow on resize,set_min_size/set_max_sizeon layout containers.Reconstruction hardening:
MenuBarmenus rebuild correctly,TabWidgettab removal respects state,MDISubWindowis restored properly.ComboBoxauto-selects the first item; dropdown raised above modal dialogs.StatusBarwidget added;Labelinteractive + context menu.FixedLayoutcontainer added: places children at fixed(x, y)offsets at their natural size (or whateverresize()set). See FixedLayout.
Bug fixes¶
Sub-widgets created during a widget’s constructor (e.g.
ScrollBarinstances insideTreeView) no longer collide with later-allocated Python widget IDs._handleCreatenow relocates a displaced occupant and reports the newnext_widto the Python side so future allocations skip past any auto-allocated IDs. This was the cause of “callback fires on the wrong widget” reports.