diff --git a/apps/dvxbasic/controls/button.dhs b/apps/dvxbasic/controls/button.dhs
new file mode 100644
index 0000000..f1dc7e2
--- /dev/null
+++ b/apps/dvxbasic/controls/button.dhs
@@ -0,0 +1,41 @@
+.topic ctrl.button
+.title CommandButton
+.toc 1 CommandButton
+.index CommandButton
+.index Button
+.index Click
+
+.h1 CommandButton
+
+VB Equivalent: CommandButton -- DVX Widget: button | Name Prefix: Command
+
+A push button that triggers an action when clicked. Created with wgtButton(parent, text).
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------ -------------------------------------------
+ Caption String The text displayed on the button. Use & for accelerator keys (e.g. "&OK").
+.endtable
+
+No additional type-specific properties beyond common properties and Caption.
+
+Default Event: Click
+
+.h2 Example
+
+.code
+Begin Form Form1
+ Caption = "Button Demo"
+ Begin CommandButton Command1
+ Caption = "&Click Me!"
+ End
+End
+
+Sub Command1_Click ()
+ MsgBox "Button was clicked!"
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/checkbox.dhs b/apps/dvxbasic/controls/checkbox.dhs
new file mode 100644
index 0000000..c92c02a
--- /dev/null
+++ b/apps/dvxbasic/controls/checkbox.dhs
@@ -0,0 +1,40 @@
+.topic ctrl.checkbox
+.title CheckBox
+.toc 1 CheckBox
+.index CheckBox
+.index Value
+
+.h1 CheckBox
+
+VB Equivalent: CheckBox -- DVX Widget: checkbox
+
+A toggle control with a label. Checked state is exposed as a Boolean.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Caption String The text displayed next to the checkbox.
+ Value Boolean True if checked, False if unchecked.
+.endtable
+
+Default Event: Click
+
+.h2 Example
+
+.code
+Begin CheckBox Check1
+ Caption = "Enable feature"
+End
+
+Sub Check1_Click ()
+ If Check1.Value Then
+ Label1.Caption = "Feature ON"
+ Else
+ Label1.Caption = "Feature OFF"
+ End If
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/combobox.dhs b/apps/dvxbasic/controls/combobox.dhs
new file mode 100644
index 0000000..6365a1c
--- /dev/null
+++ b/apps/dvxbasic/controls/combobox.dhs
@@ -0,0 +1,30 @@
+.topic ctrl.combobox
+.title ComboBox
+.toc 1 ComboBox
+.index ComboBox
+
+.h1 ComboBox
+
+VB Equivalent: ComboBox -- DVX Widget: combobox (editable text field + drop-down list, max 256 chars)
+
+A combination of a text input and a drop-down list. The user can type text or select from the list. Supports the same AddItem/RemoveItem/Clear/List methods as ListBox.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ --------- ------- -------------------------------------------
+ Text String The text in the editable field.
+ ListIndex Integer Index of the currently selected list item (-1 = none).
+ ListCount Integer Number of items in the drop-down list (read-only).
+.endtable
+
+.h2 Type-Specific Methods
+
+Same as ListBox: AddItem, RemoveItem, Clear, List.
+
+.link ctrl.listbox See ListBox for details
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/data.dhs b/apps/dvxbasic/controls/data.dhs
new file mode 100644
index 0000000..fda7445
--- /dev/null
+++ b/apps/dvxbasic/controls/data.dhs
@@ -0,0 +1,66 @@
+.topic ctrl.data
+.title Data
+.toc 1 Data Controls
+.toc 1 Data
+.index Data
+.index Database
+.index SQLite
+.index DatabaseName
+.index RecordSource
+.index MoveFirst
+.index MoveNext
+.index AddNew
+.index Reposition
+.index Validate
+
+.h1 Data
+
+VB Equivalent: Data -- DVX Widget: data (database record navigator)
+
+A data access control that connects to a SQLite database and provides record navigation. Other controls can bind to a Data control via their DataSource and DataField properties.
+
+.link ctrl.databinding See Data Binding for details
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type R/W Description
+ ------------ ------- --- -------------------------------------------
+ DatabaseName String R/W Path to the SQLite database file.
+ RecordSource String R/W Table name or SQL SELECT query for the recordset.
+ KeyColumn String R/W Primary key column name (used for UPDATE/DELETE operations).
+ Caption String R/W Text displayed on the navigator bar.
+ BOF Boolean R True if the current position is before the first record (read-only).
+ EOF Boolean R True if the current position is past the last record (read-only).
+ MasterSource String R/W Name of a master Data control (for master-detail binding).
+ MasterField String R/W Column in the master recordset to filter by.
+ DetailField String R/W Column in this recordset that matches the master field.
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ ------------ ---------- -------------------------------------------
+ MoveFirst (none) Navigate to the first record.
+ MoveLast (none) Navigate to the last record.
+ MoveNext (none) Navigate to the next record.
+ MovePrevious (none) Navigate to the previous record.
+ AddNew (none) Add a new blank record.
+ Delete (none) Delete the current record.
+ Refresh (none) Re-query the database and reload records.
+ Update (none) Write pending changes to the database.
+.endtable
+
+.h2 Type-Specific Events
+
+.table
+ Event Parameters Description
+ ---------- ----------------- -------------------------------------------
+ Reposition (none) Fires after the current record changes (navigation). This is the default event.
+ Validate Cancel As Integer Fires before writing a record. Set Cancel = 1 to abort.
+.endtable
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.databinding Data Binding
+.link ctrl.dbgrid DBGrid
diff --git a/apps/dvxbasic/controls/dbgrid.dhs b/apps/dvxbasic/controls/dbgrid.dhs
new file mode 100644
index 0000000..8203166
--- /dev/null
+++ b/apps/dvxbasic/controls/dbgrid.dhs
@@ -0,0 +1,41 @@
+.topic ctrl.dbgrid
+.title DBGrid
+.toc 1 DBGrid
+.index DBGrid
+.index Data-Bound Grid
+
+.h1 DBGrid
+
+VB Equivalent: DBGrid -- DVX Widget: dbgrid
+
+A data-bound grid that displays records from a Data control in a tabular format. Columns are auto-generated from the query results. Bind it using the DataSource property.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ ---------- ------- -------------------------------------------
+ DataSource String Name of the Data control that supplies records.
+ GridLines Boolean Show or hide grid lines between cells.
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ ------- ---------- -------------------------------------------
+ Refresh (none) Reload and redraw the grid from the Data control.
+.endtable
+
+.h2 Type-Specific Events
+
+.table
+ Event Parameters Description
+ -------- ---------- -------------------------------------------
+ Click (none) Fires when a cell is clicked.
+ DblClick (none) Fires when a cell is double-clicked. This is the default event.
+.endtable
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.data Data
+.link ctrl.databinding Data Binding
diff --git a/apps/dvxbasic/controls/dropdown.dhs b/apps/dvxbasic/controls/dropdown.dhs
new file mode 100644
index 0000000..9ac4898
--- /dev/null
+++ b/apps/dvxbasic/controls/dropdown.dhs
@@ -0,0 +1,29 @@
+.topic ctrl.dropdown
+.title DropDown
+.toc 1 DropDown
+.index DropDown
+
+.h1 DropDown
+
+DVX Extension -- DVX Widget: dropdown (non-editable drop-down list)
+
+A read-only drop-down list. Unlike ComboBox, the user cannot type free text; they can only select from the provided items. Supports AddItem/RemoveItem/Clear/List.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ --------- ------- -------------------------------------------
+ ListIndex Integer Index of the currently selected item.
+ ListCount Integer Number of items (read-only).
+.endtable
+
+.h2 Type-Specific Methods
+
+Same as ListBox: AddItem, RemoveItem, Clear, List.
+
+.link ctrl.listbox See ListBox for details
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/form.dhs b/apps/dvxbasic/controls/form.dhs
new file mode 100644
index 0000000..59313ab
--- /dev/null
+++ b/apps/dvxbasic/controls/form.dhs
@@ -0,0 +1,82 @@
+.topic ctrl.form
+.title Form
+.toc 1 Form
+.index Form
+.index Window
+.index Caption
+.index AutoSize
+.index Resizable
+.index Load
+.index Unload
+.index Show
+.index Hide
+
+.h1 Form
+
+VB Equivalent: Form -- DVX Widget: Window + VBox/HBox root
+
+The Form is the top-level container representing a DVX window. It is declared in the .frm file with Begin Form FormName. All controls are children of the form's content box, which uses either VBox (default) or HBox layout.
+
+.h2 Form Properties
+
+.table
+ Property Type Default Description
+ ---------- ------- -------------- -------------------------------------------
+ Name String "Form1" The form's name, used for event dispatch and Load statement.
+ Caption String (same as Name) Window title bar text.
+ Width Integer 400 Window width in pixels. Setting this disables AutoSize.
+ Height Integer 300 Window height in pixels. Setting this disables AutoSize.
+ Left Integer 0 Initial X position. Used when Centered is False.
+ Top Integer 0 Initial Y position. Used when Centered is False.
+ Layout String "VBox" Content box layout: "VBox" (vertical) or "HBox" (horizontal).
+ AutoSize Boolean True When True, the window shrink-wraps to fit its content.
+ Resizable Boolean True Whether the user can resize the window at runtime.
+ Centered Boolean True When True, the window is centered on screen. When False, Left/Top are used.
+.endtable
+
+.h2 Form Events
+
+.table
+ Event Parameters Description
+ ----------- --------------------- -------------------------------------------
+ Load (none) Fires after the form and all controls are created. This is the default event.
+ Unload (none) Fires when the form is being closed or unloaded.
+ QueryUnload Cancel As Integer Fires before Unload. Set Cancel = 1 to abort the close.
+ Resize (none) Fires when the window is resized by the user.
+ Activate (none) Fires when the window gains focus.
+ Deactivate (none) Fires when the window loses focus.
+.endtable
+
+.h2 Form Methods
+
+.table
+ Statement Description
+ ------------------ -------------------------------------------
+ Load FormName Load the form (creates the window and controls, fires Load event).
+ Unload FormName Unload the form (fires Unload, destroys window).
+ FormName.Show Make the form visible.
+ FormName.Show 1 Show as modal dialog (blocks until closed).
+ FormName.Hide Hide the form without unloading it.
+.endtable
+
+.h2 Example
+
+.code
+Sub Form_Load ()
+ Form1.Caption = "Hello World"
+ Print "Form loaded!"
+End Sub
+
+Sub Form_QueryUnload (Cancel As Integer)
+ If MsgBox("Really quit?", 4) <> 6 Then
+ Cancel = 1
+ End If
+End Sub
+
+Sub Form_Resize ()
+ Print "Window resized"
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.frm FRM File Format
diff --git a/apps/dvxbasic/controls/frame.dhs b/apps/dvxbasic/controls/frame.dhs
new file mode 100644
index 0000000..e5efcc7
--- /dev/null
+++ b/apps/dvxbasic/controls/frame.dhs
@@ -0,0 +1,39 @@
+.topic ctrl.frame
+.title Frame
+.toc 1 Frame
+.index Frame
+.index Container
+
+.h1 Frame
+
+VB Equivalent: Frame -- DVX Widget: frame (titled VBox container)
+
+A container with a titled border. Child controls are placed inside the frame using VBox layout. In the .frm file, nest Begin/End blocks inside the Frame block.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------ -------------------------------------------
+ Caption String The title displayed in the frame border.
+.endtable
+
+Container: Yes
+
+Default Event: Click
+
+.h2 Example
+
+.code
+Begin Frame Frame1
+ Caption = "Options"
+ Begin CheckBox Check1
+ Caption = "Option A"
+ End
+ Begin CheckBox Check2
+ Caption = "Option B"
+ End
+End
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/hbox.dhs b/apps/dvxbasic/controls/hbox.dhs
new file mode 100644
index 0000000..515c3aa
--- /dev/null
+++ b/apps/dvxbasic/controls/hbox.dhs
@@ -0,0 +1,20 @@
+.topic ctrl.hbox
+.title HBox
+.toc 1 HBox
+.index HBox
+.index Horizontal Layout
+
+.h1 HBox
+
+DVX Extension -- DVX Widget: hbox (horizontal layout container)
+
+A container that arranges its children horizontally, left to right. Use Weight on children to distribute extra space.
+
+Container: Yes
+
+Default Event: Click
+
+No type-specific properties.
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.vbox VBox
diff --git a/apps/dvxbasic/controls/hscroll.dhs b/apps/dvxbasic/controls/hscroll.dhs
new file mode 100644
index 0000000..313189e
--- /dev/null
+++ b/apps/dvxbasic/controls/hscroll.dhs
@@ -0,0 +1,35 @@
+.topic ctrl.hscrollbar
+.title HScrollBar
+.toc 1 HScrollBar
+.index HScrollBar
+.index Slider
+
+.h1 HScrollBar
+
+VB Equivalent: HScrollBar -- DVX Widget: slider | Name Prefix: HScroll
+
+A horizontal slider/scrollbar control. The value ranges between a minimum and maximum set at creation time (default 0 to 100).
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Value Integer The current slider position (clamped to min/max range).
+.endtable
+
+Default Event: Change
+
+.h2 Example
+
+.code
+Begin HScrollBar HScroll1
+ MinWidth = 200
+End
+
+Sub HScroll1_Change ()
+ Label1.Caption = "Value: " & Str$(HScroll1.Value)
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/image.dhs b/apps/dvxbasic/controls/image.dhs
new file mode 100644
index 0000000..37f5ce4
--- /dev/null
+++ b/apps/dvxbasic/controls/image.dhs
@@ -0,0 +1,26 @@
+.topic ctrl.image
+.title Image
+.toc 1 Image
+.index Image
+.index Picture
+.index BMP
+
+.h1 Image
+
+VB Equivalent: Image -- DVX Widget: image
+
+A static image display control. Loads BMP images from file. Cannot be placed via the designer toolbox (requires pixel data at creation time); typically created in code or loaded via the Picture property.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ ----------- ------- -------------------------------------------
+ Picture String Path to a BMP file to load (write-only).
+ ImageWidth Integer Width of the loaded image in pixels (read-only).
+ ImageHeight Integer Height of the loaded image in pixels (read-only).
+.endtable
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/imgbtn.dhs b/apps/dvxbasic/controls/imgbtn.dhs
new file mode 100644
index 0000000..77d778f
--- /dev/null
+++ b/apps/dvxbasic/controls/imgbtn.dhs
@@ -0,0 +1,24 @@
+.topic ctrl.imagebutton
+.title ImageButton
+.toc 1 ImageButton
+.index ImageButton
+
+.h1 ImageButton
+
+DVX Extension -- DVX Widget: imagebutton
+
+A button that displays an image instead of text. Like Image, it requires pixel data at creation time and is typically loaded via the Picture property.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ ----------- ------- -------------------------------------------
+ Picture String Path to a BMP file to load (write-only).
+ ImageWidth Integer Width of the loaded image in pixels (read-only).
+ ImageHeight Integer Height of the loaded image in pixels (read-only).
+.endtable
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/label.dhs b/apps/dvxbasic/controls/label.dhs
new file mode 100644
index 0000000..e421cad
--- /dev/null
+++ b/apps/dvxbasic/controls/label.dhs
@@ -0,0 +1,34 @@
+.topic ctrl.label
+.title Label
+.toc 1 Label
+.index Label
+.index Caption
+.index Alignment
+
+.h1 Label
+
+VB Equivalent: Label -- DVX Widget: label
+
+A static text label. Supports left, center, and right alignment.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ --------- ---- -------------------------------------------
+ Caption String The text displayed by the label.
+ Alignment Enum Text alignment: Left (default), Center, or Right.
+.endtable
+
+Default Event: Click
+
+.h2 Example
+
+.code
+Begin Label Label1
+ Caption = "Hello, World!"
+ Alignment = "Center"
+End
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/line.dhs b/apps/dvxbasic/controls/line.dhs
new file mode 100644
index 0000000..c62c200
--- /dev/null
+++ b/apps/dvxbasic/controls/line.dhs
@@ -0,0 +1,16 @@
+.topic ctrl.line
+.title Line
+.toc 1 Decorative Controls
+.toc 1 Line
+.index Line
+.index Separator
+
+.h1 Line
+
+VB Equivalent: Line -- DVX Widget: separator
+
+A visual separator line. The underlying widget supports both horizontal and vertical orientations. The default (via BASIC) is horizontal.
+
+No type-specific properties, events, or methods.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/listbox.dhs b/apps/dvxbasic/controls/listbox.dhs
new file mode 100644
index 0000000..e11a1a6
--- /dev/null
+++ b/apps/dvxbasic/controls/listbox.dhs
@@ -0,0 +1,64 @@
+.topic ctrl.listbox
+.title ListBox
+.toc 1 ListBox
+.index ListBox
+.index ListIndex
+.index ListCount
+.index AddItem
+.index RemoveItem
+
+.h1 ListBox
+
+VB Equivalent: ListBox -- DVX Widget: listbox
+
+A scrollable list of selectable items. Items are managed via methods (AddItem, RemoveItem, Clear). Supports single and multi-select modes.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ --------- ------- -------------------------------------------
+ ListIndex Integer Index of the currently selected item (-1 = no selection).
+ ListCount Integer Number of items in the list (read-only).
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ --------------- --------------------------------------- -------------------------------------------
+ AddItem Text As String Add an item to the end of the list.
+ RemoveItem Index As Integer Remove the item at the given index.
+ Clear (none) Remove all items from the list.
+ List Index As Integer Return the text of the item at the given index.
+ SelectAll (none) Select all items (multi-select mode).
+ ClearSelection (none) Deselect all items.
+ SetMultiSelect Multi As Boolean Enable or disable multi-select mode.
+ SetReorderable Reorderable As Boolean Enable or disable drag-to-reorder.
+ IsItemSelected Index As Integer Returns True if the item at Index is selected.
+ SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific item.
+.endtable
+
+Default Event: Click
+
+.h2 Example
+
+.code
+Sub Form_Load ()
+ List1.AddItem "Apple"
+ List1.AddItem "Banana"
+ List1.AddItem "Cherry"
+End Sub
+
+Sub List1_Click ()
+ Dim idx As Integer
+ idx = List1.ListIndex
+ If idx >= 0 Then
+ Label1.Caption = "Selected: " & List1.List(idx)
+ End If
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.combobox ComboBox
+.link ctrl.dropdown DropDown
diff --git a/apps/dvxbasic/controls/listview.dhs b/apps/dvxbasic/controls/listview.dhs
new file mode 100644
index 0000000..904c99b
--- /dev/null
+++ b/apps/dvxbasic/controls/listview.dhs
@@ -0,0 +1,37 @@
+.topic ctrl.listview
+.title ListView
+.toc 1 Data Display Controls
+.toc 1 ListView
+.index ListView
+.index Multi-Column List
+
+.h1 ListView
+
+VB Equivalent: ListView -- DVX Widget: listview
+
+A multi-column list with column headers. Supports sorting, multi-select, and drag-to-reorder. Columns are configured via the C API (SetColumns, SetData).
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ --------- ------- -------------------------------------------
+ ListIndex Integer Index of the currently selected row (-1 = none).
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ --------------- --------------------------------------- -------------------------------------------
+ SelectAll (none) Select all rows.
+ ClearSelection (none) Deselect all rows.
+ SetMultiSelect Multi As Boolean Enable or disable multi-select.
+ SetReorderable Reorderable As Boolean Enable or disable row reordering.
+ IsItemSelected Index As Integer Returns True if the row at Index is selected.
+ SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific row.
+.endtable
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/optbtn.dhs b/apps/dvxbasic/controls/optbtn.dhs
new file mode 100644
index 0000000..f935afb
--- /dev/null
+++ b/apps/dvxbasic/controls/optbtn.dhs
@@ -0,0 +1,33 @@
+.topic ctrl.optionbutton
+.title OptionButton
+.toc 1 OptionButton
+.index OptionButton
+.index Radio Button
+.index SetSelected
+
+.h1 OptionButton
+
+VB Equivalent: OptionButton -- DVX Widget: radio (radio group + radio button) | Name Prefix: Option
+
+A radio button for mutually exclusive choices. DVX uses a radio group container; individual OptionButtons are children of the group. The Value property returns the button's index within its group.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Caption String The text displayed next to the radio button.
+ Value Integer The index of this radio button within its group (read-only).
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ ----------- ----------------- -------------------------------------------
+ SetSelected Index As Integer Select the radio button at the given index within the group.
+.endtable
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/picbox.dhs b/apps/dvxbasic/controls/picbox.dhs
new file mode 100644
index 0000000..0ce0bf4
--- /dev/null
+++ b/apps/dvxbasic/controls/picbox.dhs
@@ -0,0 +1,26 @@
+.topic ctrl.picturebox
+.title PictureBox
+.toc 1 PictureBox
+.index PictureBox
+.index Canvas
+.index Drawing
+
+.h1 PictureBox
+
+VB Equivalent: PictureBox -- DVX Widget: canvas | Name Prefix: Picture
+
+A drawing surface (canvas). Supports drawing lines, rectangles, circles, text, and individual pixels. Can save and load BMP images. The default canvas size is 64x64 pixels.
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ ------ ---------------- -------------------------------------------
+ Clear Color As Integer Fill the entire canvas with the specified color.
+.endtable
+
+Additional drawing methods (DrawLine, DrawRect, FillRect, FillCircle, SetPixel, GetPixel, DrawText, Save, Load) are available through the C API but not currently exposed through BASIC interface descriptors.
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/progress.dhs b/apps/dvxbasic/controls/progress.dhs
new file mode 100644
index 0000000..b6a7589
--- /dev/null
+++ b/apps/dvxbasic/controls/progress.dhs
@@ -0,0 +1,22 @@
+.topic ctrl.progressbar
+.title ProgressBar
+.toc 1 ProgressBar
+.index ProgressBar
+
+.h1 ProgressBar
+
+VB Equivalent: ProgressBar -- DVX Widget: progressbar
+
+A horizontal progress indicator bar.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Value Integer Current progress value (0-100).
+.endtable
+
+No type-specific events or methods. No default event.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/scrlpane.dhs b/apps/dvxbasic/controls/scrlpane.dhs
new file mode 100644
index 0000000..e718aca
--- /dev/null
+++ b/apps/dvxbasic/controls/scrlpane.dhs
@@ -0,0 +1,25 @@
+.topic ctrl.scrollpane
+.title ScrollPane
+.toc 1 ScrollPane
+.index ScrollPane
+.index Scrollable Container
+
+.h1 ScrollPane
+
+DVX Extension -- DVX Widget: scrollpane | Name Prefix: Scroll
+
+A scrollable container. Place child controls inside and the ScrollPane automatically provides scrollbars when the content exceeds the visible area.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ NoBorder Boolean When True, suppresses the border around the scroll pane.
+.endtable
+
+Container: Yes
+
+No default event.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/spacer.dhs b/apps/dvxbasic/controls/spacer.dhs
new file mode 100644
index 0000000..4de85bd
--- /dev/null
+++ b/apps/dvxbasic/controls/spacer.dhs
@@ -0,0 +1,14 @@
+.topic ctrl.spacer
+.title Spacer
+.toc 1 Spacer
+.index Spacer
+
+.h1 Spacer
+
+DVX Extension -- DVX Widget: spacer
+
+An invisible layout spacer. Takes up space in the layout without rendering anything. Useful for pushing controls apart. Give it a Weight to absorb extra space.
+
+No type-specific properties, events, or methods.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/spinbtn.dhs b/apps/dvxbasic/controls/spinbtn.dhs
new file mode 100644
index 0000000..6e0afb9
--- /dev/null
+++ b/apps/dvxbasic/controls/spinbtn.dhs
@@ -0,0 +1,38 @@
+.topic ctrl.spinbutton
+.title SpinButton
+.toc 1 SpinButton
+.index SpinButton
+.index Spinner
+.index SetRange
+.index SetStep
+.index RealMode
+.index Decimals
+
+.h1 SpinButton
+
+DVX Extension -- DVX Widget: spinner | Name Prefix: Spin
+
+A numeric input with up/down buttons. Supports integer mode (default) and real-number mode with configurable decimal places.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Value Integer Current integer value (in integer mode).
+ RealMode Boolean True to use floating-point mode; False for integer mode.
+ Decimals Integer Number of decimal places shown in real mode.
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ -------- ------------------------------ -------------------------------------------
+ SetRange Min As Integer, Max As Integer Set the allowed value range.
+ SetStep Step As Integer Set the increment per button click.
+.endtable
+
+Default Event: Change
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/splitter.dhs b/apps/dvxbasic/controls/splitter.dhs
new file mode 100644
index 0000000..cc4ddc8
--- /dev/null
+++ b/apps/dvxbasic/controls/splitter.dhs
@@ -0,0 +1,25 @@
+.topic ctrl.splitter
+.title Splitter
+.toc 1 Splitter
+.index Splitter
+.index Split Pane
+
+.h1 Splitter
+
+DVX Extension -- DVX Widget: splitter
+
+A resizable split pane. Holds exactly two child widgets separated by a draggable divider. Default orientation is vertical (top/bottom split).
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Position Integer Position of the divider in pixels from the top (or left).
+.endtable
+
+Container: Yes
+
+No default event.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/statbar.dhs b/apps/dvxbasic/controls/statbar.dhs
new file mode 100644
index 0000000..e4cfa62
--- /dev/null
+++ b/apps/dvxbasic/controls/statbar.dhs
@@ -0,0 +1,14 @@
+.topic ctrl.statusbar
+.title StatusBar
+.toc 1 StatusBar
+.index StatusBar
+
+.h1 StatusBar
+
+VB Equivalent: StatusBar -- DVX Widget: statusbar
+
+A horizontal container styled as a status bar, typically placed at the bottom of a form. At the C API level it accepts child widgets, but it is not registered as a container in the form runtime, so child controls cannot be nested inside it in .frm files. Set its Caption property to display status text.
+
+No type-specific properties, events, or methods.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/tabstrip.dhs b/apps/dvxbasic/controls/tabstrip.dhs
new file mode 100644
index 0000000..8d5b422
--- /dev/null
+++ b/apps/dvxbasic/controls/tabstrip.dhs
@@ -0,0 +1,39 @@
+.topic ctrl.tabstrip
+.title TabStrip
+.toc 1 Tabbed and Split Controls
+.toc 1 TabStrip
+.index TabStrip
+.index TabControl
+.index SetActive
+
+.h1 TabStrip
+
+VB Equivalent: TabStrip -- DVX Widget: tabcontrol
+
+A tabbed container. Each tab page is a separate container that holds child controls. Switching tabs shows one page and hides others.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ TabIndex Integer Index of the active tab (0-based). Note: this property name collides with the common VB-compatibility TabIndex property, which shadows it at runtime. Use the SetActive method instead to switch tabs.
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ --------- ----------------- -------------------------------------------
+ SetActive Index As Integer Switch to the tab at the given index. This is the recommended way to change tabs at runtime (the TabIndex property is shadowed by the common property handler).
+.endtable
+
+Container: Yes
+
+Default Event: Click
+
+.note warning
+The TabIndex property is shadowed by the common property handler at runtime. Use the SetActive method to change tabs programmatically.
+.endnote
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/terminal.dhs b/apps/dvxbasic/controls/terminal.dhs
new file mode 100644
index 0000000..ed95f65
--- /dev/null
+++ b/apps/dvxbasic/controls/terminal.dhs
@@ -0,0 +1,36 @@
+.topic ctrl.terminal
+.title Terminal
+.toc 1 Special Controls
+.toc 1 Terminal
+.index Terminal
+.index ANSI Terminal
+.index VT100
+
+.h1 Terminal
+
+DVX Extension -- DVX Widget: ansiterm (ANSI terminal emulator)
+
+A VT100/ANSI terminal emulator widget. Supports ANSI escape sequences, scrollback buffer, and serial communication. Default size is 80 columns by 25 rows.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ ---------- ------- -------------------------------------------
+ Cols Integer Number of character columns (read-only).
+ Rows Integer Number of character rows (read-only).
+ Scrollback Integer Number of scrollback lines (write-only).
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ ------ --------------- -------------------------------------------
+ Clear (none) Clear the terminal screen.
+ Write Text As String Write text (with ANSI escape processing) to the terminal.
+.endtable
+
+No default event.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/textarea.dhs b/apps/dvxbasic/controls/textarea.dhs
new file mode 100644
index 0000000..397b378
--- /dev/null
+++ b/apps/dvxbasic/controls/textarea.dhs
@@ -0,0 +1,22 @@
+.topic ctrl.textarea
+.title TextArea
+.toc 1 TextArea
+.index TextArea
+
+.h1 TextArea
+
+VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, max 4096 chars)
+
+A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports syntax colorization, line numbers, auto-indent, and find/replace via the C API.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------ -------------------------------------------
+ Text String The full text content.
+.endtable
+
+Default Event: Change
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/textbox.dhs b/apps/dvxbasic/controls/textbox.dhs
new file mode 100644
index 0000000..87f79c2
--- /dev/null
+++ b/apps/dvxbasic/controls/textbox.dhs
@@ -0,0 +1,40 @@
+.topic ctrl.textbox
+.title TextBox
+.toc 1 TextBox
+.index TextBox
+.index Text
+.index DataSource
+.index DataField
+
+.h1 TextBox
+
+VB Equivalent: TextBox -- DVX Widget: textbox (single-line text input, max 256 chars)
+
+A single-line text input field. Supports data binding via DataSource and DataField properties.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ ---------- ------ -------------------------------------------
+ Text String The text content of the input field.
+ DataSource String Name of a Data control for data binding.
+ DataField String Column name for data binding.
+.endtable
+
+Default Event: Change
+
+.h2 Example
+
+.code
+Begin TextBox Text1
+ Text = "Enter text here"
+End
+
+Sub Text1_Change ()
+ Label1.Caption = "You typed: " & Text1.Text
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.databinding Data Binding
diff --git a/apps/dvxbasic/controls/timer.dhs b/apps/dvxbasic/controls/timer.dhs
new file mode 100644
index 0000000..e13b490
--- /dev/null
+++ b/apps/dvxbasic/controls/timer.dhs
@@ -0,0 +1,61 @@
+.topic ctrl.timer
+.title Timer
+.toc 1 Timer
+.index Timer
+.index Interval
+.index Start
+.index Stop
+
+.h1 Timer
+
+VB Equivalent: Timer -- DVX Widget: timer (non-visual)
+
+A non-visual control that fires its event at a regular interval. The Timer widget is invisible at runtime -- it has no on-screen representation.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Enabled Boolean True to start the timer, False to stop it.
+ Interval Integer Timer interval in milliseconds (write-only from BASIC).
+.endtable
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ ------ ---------- -------------------------------------------
+ Start (none) Start the timer.
+ Stop (none) Stop the timer.
+.endtable
+
+.h2 Type-Specific Events
+
+.table
+ Event Parameters Description
+ ----- ---------- -------------------------------------------
+ Timer (none) Fires each time the interval elapses. This is the default event.
+.endtable
+
+.note info
+The Timer control fires the Timer event instead of Change. The onChange callback on the underlying widget is remapped automatically.
+.endnote
+
+.h2 Example
+
+.code
+Begin Timer Timer1
+ Interval = 1000
+ Enabled = True
+End
+
+Dim counter As Integer
+
+Sub Timer1_Timer ()
+ counter = counter + 1
+ Label1.Caption = "Ticks: " & Str$(counter)
+End Sub
+.endcode
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/toolbar.dhs b/apps/dvxbasic/controls/toolbar.dhs
new file mode 100644
index 0000000..162ac5b
--- /dev/null
+++ b/apps/dvxbasic/controls/toolbar.dhs
@@ -0,0 +1,17 @@
+.topic ctrl.toolbar
+.title Toolbar
+.toc 1 Bar Controls
+.toc 1 Toolbar
+.index Toolbar
+
+.h1 Toolbar
+
+VB Equivalent: Toolbar -- DVX Widget: toolbar
+
+A horizontal container styled as a toolbar, with compact padding and spacing. Place buttons, labels, or other controls inside.
+
+Container: Yes
+
+No type-specific properties, events, or methods.
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/treeview.dhs b/apps/dvxbasic/controls/treeview.dhs
new file mode 100644
index 0000000..0578c28
--- /dev/null
+++ b/apps/dvxbasic/controls/treeview.dhs
@@ -0,0 +1,23 @@
+.topic ctrl.treeview
+.title TreeView
+.toc 1 TreeView
+.index TreeView
+
+.h1 TreeView
+
+VB Equivalent: TreeView -- DVX Widget: treeview
+
+A hierarchical tree of expandable/collapsible nodes. Nodes are created via the C API (wgtTreeItem). Supports multi-select and drag-to-reorder.
+
+.h2 Type-Specific Methods
+
+.table
+ Method Parameters Description
+ -------------- ---------------------- -------------------------------------------
+ SetMultiSelect Multi As Boolean Enable or disable multi-select mode.
+ SetReorderable Reorderable As Boolean Enable or disable node reordering.
+.endtable
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/controls/vbox.dhs b/apps/dvxbasic/controls/vbox.dhs
new file mode 100644
index 0000000..65791a0
--- /dev/null
+++ b/apps/dvxbasic/controls/vbox.dhs
@@ -0,0 +1,20 @@
+.topic ctrl.vbox
+.title VBox
+.toc 1 VBox
+.index VBox
+.index Vertical Layout
+
+.h1 VBox
+
+DVX Extension -- DVX Widget: vbox (vertical layout container)
+
+A container that arranges its children vertically, top to bottom. No title or border. Use Weight on children to distribute extra space.
+
+Container: Yes
+
+Default Event: Click
+
+No type-specific properties.
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.hbox HBox
diff --git a/apps/dvxbasic/controls/wrapbox.dhs b/apps/dvxbasic/controls/wrapbox.dhs
new file mode 100644
index 0000000..3cf6f96
--- /dev/null
+++ b/apps/dvxbasic/controls/wrapbox.dhs
@@ -0,0 +1,25 @@
+.topic ctrl.wrapbox
+.title WrapBox
+.toc 1 WrapBox
+.index WrapBox
+.index Flow Layout
+
+.h1 WrapBox
+
+DVX Extension -- DVX Widget: wrapbox
+
+A container that arranges children in a flowing layout, wrapping to the next row when the available width is exceeded. Similar to CSS flexbox with flex-wrap: wrap.
+
+.h2 Type-Specific Properties
+
+.table
+ Property Type Description
+ --------- ---- -------------------------------------------
+ Alignment Enum Horizontal alignment of items: Left, Center, or Right.
+.endtable
+
+Container: Yes
+
+Default Event: Click
+
+.link ctrl.common.props Common Properties, Events, and Methods
diff --git a/apps/dvxbasic/ctrlover.dhs b/apps/dvxbasic/ctrlover.dhs
new file mode 100644
index 0000000..bf2fe89
--- /dev/null
+++ b/apps/dvxbasic/ctrlover.dhs
@@ -0,0 +1,397 @@
+.topic ctrl.common.props
+.title Common Properties, Events, and Methods
+.toc 0 Common Properties, Events, and Methods
+.default
+.index Common Properties
+.index Common Events
+.index Common Methods
+.index Properties
+.index Events
+.index Methods
+
+.h1 Common Properties, Events, and Methods
+
+Every control in DVX BASIC inherits a set of common properties, events, and methods. These are handled by the form runtime before dispatching to widget-specific interface descriptors.
+
+.h2 Common Properties
+
+.table
+ Property Type R/W Description
+ ---------- ------- --- -------------------------------------------
+ Name String R The control's name (e.g. "Command1"). Read-only at runtime.
+ Left Integer R/W X position in pixels relative to the parent container.
+ Top Integer R/W Y position in pixels relative to the parent container.
+ Width Integer R/W Current width in pixels. Setting this changes the minimum width constraint.
+ Height Integer R/W Current height in pixels. Setting this changes the minimum height constraint.
+ MinWidth Integer R/W Minimum width for layout. Alias for Width in the setter.
+ MinHeight Integer R/W Minimum height for layout. Alias for Height in the setter.
+ MaxWidth Integer R/W Maximum width cap (0 = no limit, stretch to fill).
+ MaxHeight Integer R/W Maximum height cap (0 = no limit, stretch to fill).
+ Weight Integer R/W Layout weight. 0 = fixed size, >0 = share extra space proportionally.
+ Visible Boolean R/W Whether the control is visible.
+ Enabled Boolean R/W Whether the control accepts user input.
+ BackColor Long R/W Background color as a 32-bit ARGB value.
+ ForeColor Long R/W Foreground (text) color as a 32-bit ARGB value.
+ TabIndex Integer R Accepted for VB compatibility but ignored. DVX has no tab order.
+.endtable
+
+.h2 Common Events
+
+These events are wired on every control loaded from a .frm file. Controls created dynamically at runtime via code only receive Click, DblClick, Change, GotFocus, and LostFocus; the keyboard, mouse, and scroll events below require the control to be defined in the .frm file.
+
+.table
+ Event Parameters Description
+ --------- ------------------------------------------- -------------------------------------------
+ Click (none) Fires when the control is clicked.
+ DblClick (none) Fires when the control is double-clicked.
+ Change (none) Fires when the control's value or text changes.
+ GotFocus (none) Fires when the control receives keyboard focus.
+ LostFocus (none) Fires when the control loses keyboard focus.
+ KeyPress KeyAscii As Integer Fires when a printable key is pressed. KeyAscii is the ASCII code.
+ KeyDown KeyCode As Integer, Shift As Integer Fires when any key is pressed down. KeyCode is the scan code; Shift indicates modifier keys.
+ KeyUp KeyCode As Integer, Shift As Integer Fires when a key is released.
+ MouseDown Button As Integer, X As Integer, Y As Integer Fires when a mouse button is pressed over the control.
+ MouseUp Button As Integer, X As Integer, Y As Integer Fires when a mouse button is released over the control.
+ MouseMove Button As Integer, X As Integer, Y As Integer Fires when the mouse moves over the control.
+ Scroll Delta As Integer Fires when the control is scrolled (mouse wheel or scrollbar).
+.endtable
+
+.h2 Common Methods
+
+.table
+ Method Parameters Description
+ -------- ---------- -------------------------------------------
+ SetFocus (none) Gives keyboard focus to this control.
+ Refresh (none) Forces the control to repaint.
+.endtable
+
+.link ctrl.form Form
+.link ctrl.databinding Data Binding
+.link ctrl.frm FRM File Format
+
+
+.topic ctrl.databinding
+.title Data Binding
+.toc 1 Data Binding
+.index Data Binding
+.index DataSource
+.index DataField
+.index Master-Detail
+
+.h1 Data Binding
+
+DVX BASIC provides VB3-style data binding through three properties that can be set on most controls:
+
+.table
+ Property Set On Description
+ ---------- ----------- -------------------------------------------
+ DataSource Any control Name of the Data control to bind to (e.g. "Data1").
+ DataField Any control Column name from the Data control's recordset to display.
+.endtable
+
+.h2 How It Works
+
+.list
+.item Place a Data control on the form and set its DatabaseName and RecordSource properties.
+.item Place one or more display/edit controls (TextBox, Label, etc.) and set their DataSource to the Data control's name and DataField to a column name.
+.item When the form loads, the Data control auto-refreshes: it opens the database, runs the query, and navigates to the first record.
+.item Bound controls are updated automatically each time the Data control repositions (the Reposition event fires, and the runtime pushes the current record's field values into all bound controls).
+.item When a bound control loses focus (LostFocus), its current text is written back to the Data control's record cache, and Update is called automatically to persist changes.
+.endlist
+
+.h2 Master-Detail Binding
+
+For hierarchical data (e.g. orders and order items), use two Data controls:
+
+.list
+.item A master Data control bound to the parent table.
+.item A detail Data control with its MasterSource set to the master's name, MasterField set to the key column in the master, and DetailField set to the foreign key column in the detail table.
+.endlist
+
+When the master record changes, the detail Data control automatically re-queries using the master's current value for filtering. All controls bound to the detail are refreshed.
+
+.h2 DBGrid Binding
+
+Set the DBGrid's DataSource to a Data control name. The grid auto-populates columns from the query results and refreshes whenever the Data control refreshes.
+
+.h2 Example
+
+.code
+VERSION DVX 1.00
+Begin Form frmData
+ Caption = "Data Binding Example"
+ AutoSize = False
+ Width = 400
+ Height = 280
+ Begin Data Data1
+ DatabaseName = "myapp.db"
+ RecordSource = "customers"
+ End
+ Begin Label lblName
+ Caption = "Name:"
+ End
+ Begin TextBox txtName
+ DataSource = "Data1"
+ DataField = "name"
+ End
+ Begin Label lblEmail
+ Caption = "Email:"
+ End
+ Begin TextBox txtEmail
+ DataSource = "Data1"
+ DataField = "email"
+ End
+End
+
+Sub Data1_Reposition ()
+ Print "Current record changed"
+End Sub
+
+Sub Data1_Validate (Cancel As Integer)
+ If txtName.Text = "" Then
+ MsgBox "Name cannot be empty!"
+ Cancel = 1
+ End If
+End Sub
+.endcode
+
+.link ctrl.data Data
+.link ctrl.dbgrid DBGrid
+
+
+.topic ctrl.menus
+.title Menu System
+.toc 1 Menu System
+.index Menu
+.index Menu Bar
+.index Submenu
+.index Separator
+
+.h1 Menu System
+
+Menus are defined in the .frm file using Begin Menu blocks. Each menu item has a name, caption, and nesting level. Menu items fire Click events dispatched as MenuName_Click.
+
+.h2 FRM Syntax
+
+.code
+Begin Form Form1
+ Caption = "Menu Demo"
+
+ Begin Menu mnuFile
+ Caption = "&File"
+ Begin Menu mnuOpen
+ Caption = "&Open"
+ End
+ Begin Menu mnuSave
+ Caption = "&Save"
+ End
+ Begin Menu mnuSep1
+ Caption = "-"
+ End
+ Begin Menu mnuExit
+ Caption = "E&xit"
+ End
+ End
+
+ Begin Menu mnuEdit
+ Caption = "&Edit"
+ Begin Menu mnuCopy
+ Caption = "&Copy"
+ End
+ Begin Menu mnuPaste
+ Caption = "&Paste"
+ End
+ End
+End
+.endcode
+
+.h2 Menu Item Properties
+
+.table
+ Property Type Description
+ -------- ------- -------------------------------------------
+ Caption String The text displayed. Use & for accelerator key. Set to "-" for a separator.
+ Checked Boolean Whether the menu item shows a checkmark.
+ Enabled Boolean Whether the menu item is enabled (default True).
+.endtable
+
+.h2 Nesting
+
+Menu items are nested by placing Begin Menu blocks inside other Begin Menu blocks:
+
+.list
+.item Level 0: top-level menu bar headers (e.g. "File", "Edit").
+.item Level 1: items within a top-level menu.
+.item Level 2+: submenu items.
+.endlist
+
+A level-0 menu that contains children becomes a top-level menu header. A non-level-0 menu that contains children becomes a submenu.
+
+.h2 Event Dispatch
+
+Each clickable menu item (not headers, not separators) receives a unique numeric ID at load time. When clicked, the form's onMenu handler maps the ID to the menu item's name and fires MenuName_Click.
+
+.code
+Sub mnuOpen_Click ()
+ MsgBox "Open was clicked"
+End Sub
+
+Sub mnuExit_Click ()
+ Unload Form1
+End Sub
+.endcode
+
+.link ctrl.form Form
+.link ctrl.frm FRM File Format
+
+
+.topic ctrl.arrays
+.title Control Arrays
+.toc 1 Control Arrays
+.index Control Arrays
+.index Index Property
+
+.h1 Control Arrays
+
+DVX BASIC supports VB-style control arrays. Multiple controls can share the same name, differentiated by an Index property. When an event fires on a control array element, the element's index is passed as the first parameter.
+
+.h2 Defining Control Arrays in FRM
+
+.code
+Begin CommandButton Command1
+ Caption = "Button A"
+ Index = 0
+End
+Begin CommandButton Command1
+ Caption = "Button B"
+ Index = 1
+End
+Begin CommandButton Command1
+ Caption = "Button C"
+ Index = 2
+End
+.endcode
+
+.h2 Event Handler Convention
+
+When a control has an Index property (>= 0), the event handler receives Index As Integer as the first parameter, before any event-specific parameters.
+
+.code
+Sub Command1_Click (Index As Integer)
+ Select Case Index
+ Case 0
+ MsgBox "Button A clicked"
+ Case 1
+ MsgBox "Button B clicked"
+ Case 2
+ MsgBox "Button C clicked"
+ End Select
+End Sub
+.endcode
+
+.h2 Accessing Array Elements in Code
+
+Use the indexed form ControlName(Index) to access a specific element:
+
+.code
+Command1(0).Caption = "New Text"
+Command1(1).Enabled = False
+.endcode
+
+.note info
+Control array elements share the same event handler Sub. The runtime prepends the Index argument automatically. If you define parameters on the Sub, Index comes first, followed by the event's own parameters (e.g. KeyPress would be Sub Ctrl1_KeyPress (Index As Integer, KeyAscii As Integer)).
+.endnote
+
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.frm FRM File Format
+
+
+.topic ctrl.frm
+.title FRM File Format
+.toc 1 FRM File Format
+.index FRM
+.index .frm
+.index Form File
+
+.h1 FRM File Format
+
+The .frm file is a text file that describes a form's layout, controls, menus, and code. It follows a format compatible with VB3 .frm files, with DVX-specific extensions.
+
+.h2 Structure
+
+.code
+VERSION DVX 1.00
+Begin Form FormName
+ form-level properties...
+
+ Begin Menu mnuFile
+ Caption = "&File"
+ Begin Menu mnuOpen
+ Caption = "&Open"
+ End
+ End
+
+ Begin TypeName ControlName
+ property = value
+ ...
+ End
+
+ Begin Frame Frame1
+ Caption = "Group"
+ Begin TypeName ChildName
+ ...
+ End
+ End
+End
+
+BASIC code follows...
+
+Sub FormName_Load ()
+ ...
+End Sub
+.endcode
+
+.h2 Rules
+
+.list
+.item The VERSION line is optional. VERSION DVX 1.00 marks a native DVX form. VB forms with version <= 2.0 are accepted for import.
+.item The form block begins with Begin Form Name and ends with End.
+.item Controls are nested with Begin TypeName Name / End.
+.item Container controls (Frame, VBox, HBox, Toolbar, TabStrip, ScrollPane, Splitter, WrapBox) can have child controls nested inside them.
+.item Properties are assigned as Key = Value. String values are optionally quoted.
+.item Everything after the form's closing End is BASIC source code.
+.item Comments in the form section use ' (single quote).
+.item Blank lines are ignored in the form section.
+.endlist
+
+.h2 Common FRM Properties
+
+.table
+ Property Applies To Description
+ ----------------------- --------------- -------------------------------------------
+ Caption Form, controls Display text or window title.
+ Text TextBox, ComboBox Initial text content.
+ MinWidth / Width Controls Minimum width. Both names are accepted.
+ MinHeight / Height Controls Minimum height. Both names are accepted.
+ MaxWidth Controls Maximum width (0 = no cap).
+ MaxHeight Controls Maximum height (0 = no cap).
+ Weight Controls Layout weight for flexible sizing.
+ Left Form, controls X position (used by Form when Centered=False; informational for controls).
+ Top Form, controls Y position.
+ Index Controls Control array index (-1 or absent = not in array).
+ Visible Controls Initial visibility.
+ Enabled Controls Initial enabled state.
+ Layout Form "VBox" or "HBox".
+ AutoSize Form Auto-fit window to content.
+ Resizable Form Allow runtime resizing.
+ Centered Form Center window on screen.
+ DatabaseName Data SQLite database file path.
+ RecordSource Data Table name or SQL query.
+ DataSource Bound controls Name of the Data control.
+ DataField Bound controls Column name in the recordset.
+.endtable
+
+.link ctrl.form Form
+.link ctrl.common.props Common Properties, Events, and Methods
+.link ctrl.menus Menu System
+.link ctrl.arrays Control Arrays
diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c
index 9b23c80..4396ce6 100644
--- a/apps/dvxbasic/ide/ideMain.c
+++ b/apps/dvxbasic/ide/ideMain.c
@@ -110,6 +110,8 @@
#define CMD_WIN_WATCH 154
#define CMD_WIN_BREAKPOINTS 155
#define CMD_DEBUG_LAYOUT 156
+#define CMD_HELP_CONTENTS 157
+#define CMD_HELP_API 158
#define IDE_MAX_IMM 1024
#define IDE_DESIGN_W 400
#define IDE_DESIGN_H 300
@@ -560,6 +562,9 @@ int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
sAc = ctx->shellCtx;
+ // Set help file for F1
+ snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, "dvxbasic.hlp");
+
basStringSystemInit();
prjInit(&sProject);
buildWindow();
@@ -740,6 +745,9 @@ static void buildWindow(void) {
wmAddMenuCheckItem(toolsMenu, "Debug &Layout", CMD_DEBUG_LAYOUT, false);
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
+ wmAddMenuItem(helpMenu, "&DVX BASIC Help\tF1", CMD_HELP_CONTENTS);
+ wmAddMenuItem(helpMenu, "DVX &API Reference", CMD_HELP_API);
+ wmAddMenuSeparator(helpMenu);
wmAddMenuItem(helpMenu, "&About DVX BASIC...", CMD_HELP_ABOUT);
AccelTableT *accel = dvxCreateAccelTable();
@@ -4537,6 +4545,22 @@ static void onMenu(WindowT *win, int32_t menuId) {
wgtSetDebugLayout(sAc, wmMenuItemIsChecked(sWin->menuBar, CMD_DEBUG_LAYOUT));
}
+ if (menuId == CMD_HELP_CONTENTS) {
+ char hlpPath[DVX_MAX_PATH];
+ char viewerPath[DVX_MAX_PATH];
+ snprintf(hlpPath, sizeof(hlpPath), "%s%c%s", sCtx->appDir, DVX_PATH_SEP, "dvxbasic.hlp");
+ snprintf(viewerPath, sizeof(viewerPath), "APPS%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP);
+ shellLoadAppWithArgs(sAc, viewerPath, hlpPath);
+ }
+
+ if (menuId == CMD_HELP_API) {
+ char viewerPath[DVX_MAX_PATH];
+ char sysHlp[DVX_MAX_PATH];
+ snprintf(viewerPath, sizeof(viewerPath), "APPS%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP);
+ snprintf(sysHlp, sizeof(sysHlp), "APPS%cPROGMAN%cDVXHELP.HLP", DVX_PATH_SEP, DVX_PATH_SEP);
+ shellLoadAppWithArgs(sAc, viewerPath, sysHlp);
+ }
+
if (menuId == CMD_HELP_ABOUT) {
dvxMessageBox(sAc, "About DVX BASIC",
"DVX BASIC 1.0\n"
diff --git a/docs/src/dvxbasic_ide_guide.dvxhelp b/apps/dvxbasic/ideguide.dhs
similarity index 98%
rename from docs/src/dvxbasic_ide_guide.dvxhelp
rename to apps/dvxbasic/ideguide.dhs
index 1a73f69..a9982a2 100644
--- a/docs/src/dvxbasic_ide_guide.dvxhelp
+++ b/apps/dvxbasic/ideguide.dhs
@@ -57,8 +57,8 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.file
.title File Menu
-.toc 0 Menu Reference
-.toc 1 File Menu
+.toc 1 Menu Reference
+.toc 2 File Menu
.index File Menu
.index New Project
.index Open Project
@@ -97,7 +97,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.edit
.title Edit Menu
-.toc 1 Edit Menu
+.toc 2 Edit Menu
.index Edit Menu
.index Cut
.index Copy
@@ -131,7 +131,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.run
.title Run Menu
-.toc 1 Run Menu
+.toc 2 Run Menu
.index Run Menu
.index Run
.index Debug
@@ -172,7 +172,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.view
.title View Menu
-.toc 1 View Menu
+.toc 2 View Menu
.index View Menu
.index Code View
.index Design View
@@ -199,7 +199,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.window
.title Window Menu
-.toc 1 Window Menu
+.toc 2 Window Menu
.index Window Menu
.h1 Window Menu
@@ -225,7 +225,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.tools
.title Tools Menu
-.toc 1 Tools Menu
+.toc 2 Tools Menu
.index Tools Menu
.index Preferences
.index Debug Layout
@@ -245,7 +245,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.menu.help
.title Help Menu
-.toc 1 Help Menu
+.toc 2 Help Menu
.index Help Menu
.index About
@@ -261,7 +261,7 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual
.topic ide.toolbar
.title Toolbar
-.toc 0 Toolbar
+.toc 1 Toolbar
.index Toolbar
.h1 Toolbar
@@ -311,7 +311,7 @@ The toolbar is organized into four groups separated by vertical dividers. Each b
.topic ide.editor
.title Code Editor
-.toc 0 Code Editor
+.toc 1 Code Editor
.index Code Editor
.index Syntax Highlighting
.index Object Dropdown
@@ -363,7 +363,7 @@ The editor applies real-time syntax coloring as you type. The following categori
.topic ide.designer
.title Form Designer
-.toc 0 Form Designer
+.toc 1 Form Designer
.index Form Designer
.index Design Surface
.index Grid Snapping
@@ -397,7 +397,7 @@ Forms have the following design-time properties: Name, Caption, Width, Height, L
.topic ide.project
.title Project System
-.toc 0 Project System
+.toc 1 Project System
.index Project System
.index Project Files
.index .dbp Files
@@ -443,7 +443,7 @@ The Project Explorer is a tree view window listing all project files. Double-cli
.topic ide.properties
.title Properties Panel
-.toc 0 Properties Panel
+.toc 1 Properties Panel
.index Properties Panel
.index Control Properties
.index Property List
@@ -465,7 +465,7 @@ Each control type exposes different properties (e.g., Caption, Text, Width, Heig
.topic ide.toolbox
.title Toolbox
-.toc 0 Toolbox
+.toc 1 Toolbox
.index Toolbox
.index Controls
.index Widget Palette
@@ -520,7 +520,7 @@ Click a tool to select it (the active tool name is stored in the designer state)
.topic ide.debugger
.title Debugger
-.toc 0 Debugger
+.toc 1 Debugger
.index Debugger
.index Breakpoints
.index Stepping
@@ -618,8 +618,8 @@ Press Esc or click the Stop toolbar button at any time to halt execution. The VM
.topic ide.debug.locals
.title Locals Window
-.toc 0 Debug Windows
-.toc 1 Locals Window
+.toc 1 Debug Windows
+.toc 2 Locals Window
.index Locals Window
.index Variable Inspection
@@ -652,7 +652,7 @@ Up to 64 variables are displayed. The window is resizable.
.topic ide.debug.callstack
.title Call Stack Window
-.toc 1 Call Stack Window
+.toc 2 Call Stack Window
.index Call Stack
.index Stack Trace
@@ -674,7 +674,7 @@ The current location is shown first, followed by each caller in the call stack (
.topic ide.debug.watch
.title Watch Window
-.toc 1 Watch Window
+.toc 2 Watch Window
.index Watch Window
.index Watch Expressions
@@ -717,7 +717,7 @@ Watch expressions support:
.topic ide.debug.breakpoints
.title Breakpoints Window
-.toc 1 Breakpoints Window
+.toc 2 Breakpoints Window
.index Breakpoints Window
.h1 Breakpoints Window
@@ -742,7 +742,7 @@ Lists all set breakpoints as a three-column ListView:
.topic ide.immediate
.title Immediate Window
-.toc 0 Immediate Window
+.toc 1 Immediate Window
.index Immediate Window
.index REPL
.index Expression Evaluation
@@ -801,7 +801,7 @@ If the assignment target cannot be resolved (unknown variable, out-of-bounds ind
.topic ide.output
.title Output Window
-.toc 0 Output Window
+.toc 1 Output Window
.index Output Window
.index PRINT Output
.index Runtime Errors
@@ -825,7 +825,7 @@ INPUT statements prompt the user via a modal InputBox dialog; the prompt text is
.topic ide.findreplace
.title Find / Replace
-.toc 0 Find / Replace
+.toc 1 Find / Replace
.index Find
.index Replace
.index Find Next
@@ -866,7 +866,7 @@ F3 repeats the last search (Find Next) without opening the dialog.
.topic ide.preferences
.title Preferences
-.toc 0 Preferences
+.toc 1 Preferences
.index Preferences
.index Settings
.index Tab Width
@@ -905,7 +905,7 @@ These fields set the default values for new project metadata:
.topic ide.shortcuts
.title Keyboard Shortcuts
-.toc 0 Keyboard Shortcuts
+.toc 1 Keyboard Shortcuts
.index Keyboard Shortcuts
.index Hotkeys
.index Accelerators
diff --git a/docs/src/dvxbasic_language_reference.dvxhelp b/apps/dvxbasic/langref.dhs
similarity index 98%
rename from docs/src/dvxbasic_language_reference.dvxhelp
rename to apps/dvxbasic/langref.dhs
index 9aaf3ad..d252211 100644
--- a/docs/src/dvxbasic_language_reference.dvxhelp
+++ b/apps/dvxbasic/langref.dhs
@@ -73,7 +73,7 @@ When mixing types in expressions, values are automatically promoted to a common
.topic lang.operators
.title Operators
-.toc 0 Operators
+.toc 1 Operators
.index Operators
.index Precedence
.index AND
@@ -116,7 +116,7 @@ result$ = firstName$ & " " & lastName$
.topic lang.statements
.title Statements Overview
-.toc 0 Statements
+.toc 1 Statements
.index Statements
.index REM
.index Comments
@@ -135,7 +135,7 @@ Multiple statements can appear on one line separated by :. Lines can be continue
.topic lang.declarations
.title Declaration Statements
-.toc 1 Declarations
+.toc 2 Declarations
.index DIM
.index REDIM
.index CONST
@@ -352,7 +352,7 @@ ERASE arrayName
.topic lang.conditionals
.title Conditional Statements
-.toc 1 Conditionals
+.toc 2 Conditionals
.index IF
.index THEN
.index ELSE
@@ -438,7 +438,7 @@ CASE items can be combined with commas. The IS keyword allows comparison operato
.topic lang.loops
.title Loop Statements
-.toc 1 Loops
+.toc 2 Loops
.index FOR
.index NEXT
.index STEP
@@ -521,7 +521,7 @@ Wend
.topic lang.procedures
.title Procedures
-.toc 1 Procedures
+.toc 2 Procedures
.index SUB
.index END SUB
.index FUNCTION
@@ -586,7 +586,7 @@ Print FnSquare(5) ' prints 25
.topic lang.flow
.title Flow Control
-.toc 1 Flow Control
+.toc 2 Flow Control
.index EXIT
.index CALL
.index GOTO
@@ -653,7 +653,7 @@ If the expression evaluates to 1, control goes to the first label; 2, the second
.topic lang.io
.title Input/Output Statements
-.toc 1 Input/Output
+.toc 2 Input/Output
.index PRINT
.index INPUT
.index DATA
@@ -731,7 +731,7 @@ Restore
.topic lang.misc
.title Miscellaneous Statements
-.toc 1 Miscellaneous Statements
+.toc 2 Miscellaneous Statements
.index ON ERROR
.index RESUME
.index RESUME NEXT
@@ -809,7 +809,7 @@ END
.topic lang.fileio
.title File I/O
-.toc 0 File I/O
+.toc 1 File I/O
.index OPEN
.index CLOSE
.index PRINT #
@@ -914,8 +914,8 @@ pos = SEEK(channel) ' Function: get current position
.topic lang.func.string
.title String Functions
-.toc 0 Built-in Functions
-.toc 1 String Functions
+.toc 1 Built-in Functions
+.toc 2 String Functions
.index ASC
.index CHR$
.index FORMAT$
@@ -971,7 +971,7 @@ Mid$(s$, 3, 2) = "XY" ' Replace 2 characters starting at position 3
.topic lang.func.math
.title Math Functions
-.toc 1 Math Functions
+.toc 2 Math Functions
.index ABS
.index ATN
.index COS
@@ -1012,7 +1012,7 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu
.topic lang.func.conversion
.title Conversion Functions
-.toc 1 Conversion Functions
+.toc 2 Conversion Functions
.index CDBL
.index CINT
.index CLNG
@@ -1033,7 +1033,7 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu
.topic lang.func.fileio
.title File I/O Functions
-.toc 1 File I/O Functions
+.toc 2 File I/O Functions
.index EOF
.index FREEFILE
.index INPUT$
@@ -1059,7 +1059,7 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu
.topic lang.func.misc
.title Miscellaneous Functions
-.toc 1 Miscellaneous Functions
+.toc 2 Miscellaneous Functions
.index DATE$
.index TIME$
.index ENVIRON$
@@ -1077,7 +1077,7 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu
.topic lang.forms
.title Form and Control Statements
-.toc 0 Form and Control Statements
+.toc 1 Form and Control Statements
.index LOAD
.index UNLOAD
.index Show
@@ -1254,7 +1254,7 @@ End Sub
.topic lang.sql
.title SQL Functions
-.toc 0 SQL Functions
+.toc 1 SQL Functions
.index SQLOpen
.index SQLClose
.index SQLExec
@@ -1433,7 +1433,7 @@ SQLClose db
.topic lang.app
.title App Object
-.toc 0 App Object
+.toc 1 App Object
.index App
.index App.Path
.index App.Config
@@ -1459,7 +1459,7 @@ Print "Running from: " & App.Path
.topic lang.ini
.title INI Functions
-.toc 0 INI Functions
+.toc 1 INI Functions
.index IniRead
.index IniWrite
.index INI
@@ -1496,7 +1496,7 @@ IniWrite App.Config & "\app.ini", "Display", "FontSize", Str$(fontSize)
.topic lang.constants
.title Predefined Constants
-.toc 0 Predefined Constants
+.toc 1 Predefined Constants
.index vbOKOnly
.index vbOKCancel
.index vbYesNo
diff --git a/apps/dvxhelp/dvxhelp.c b/apps/dvxhelp/dvxhelp.c
index b1cf7ae..80aa92e 100644
--- a/apps/dvxhelp/dvxhelp.c
+++ b/apps/dvxhelp/dvxhelp.c
@@ -46,15 +46,17 @@
#define HELP_SPLITTER_POS 160
#define HELP_TOC_MIN_W 100
#define HELP_CONTENT_PAD 6
-#define HELP_HEADING1_PAD 4
-#define HELP_HEADING2_PAD 2
+#define HELP_PARA_SPACING 12
+#define HELP_HEADING1_TOP 24
+#define HELP_HEADING1_PAD 6
+#define HELP_HEADING2_TOP 16
+#define HELP_HEADING2_PAD 4
+#define HELP_HEADING3_TOP 8
#define HELP_LINK_PAD_X 2
#define HELP_LINK_PAD_Y 1
#define HELP_NOTE_PAD 6
#define HELP_NOTE_PREFIX_W 4
#define HELP_CODE_PAD 4
-#define HELP_LIST_INDENT 12
-#define HELP_LIST_BULLET_W 8
#define MAX_HISTORY 64
#define MAX_SEARCH_BUF 128
@@ -63,6 +65,7 @@
#define CMD_FORWARD 101
#define CMD_INDEX 102
#define CMD_SEARCH 103
+#define CMD_EXIT 104
// ============================================================
// Module state
@@ -91,6 +94,12 @@ static int32_t sHistoryPos = -1;
static int32_t sHistoryCount = 0;
static int32_t sCurrentTopic = -1;
+// Viewport wrap width — the width text should wrap at, derived from the
+// ScrollPane's inner area. Updated when the window is first laid out and
+// on resize. Wrapping widgets use this instead of w->parent->w so that
+// wide tables/code don't inflate the wrap width.
+static int32_t sWrapWidth = 0;
+
// Custom widget type IDs
static int32_t sHelpTextTypeId = -1;
static int32_t sHelpHeadingTypeId = -1;
@@ -105,8 +114,10 @@ static int32_t sHelpListItemTypeId = -1;
// ============================================================
typedef struct {
- char *text;
- int32_t lineCount;
+ char *text; // original flowing text (no newlines)
+ char *wrapped; // cached wrapped text (with \n), NULL = needs wrap
+ int32_t lineCount; // line count of wrapped text
+ int32_t wrapWidth; // pixel width text was wrapped at (-1 = dirty)
} HelpTextDataT;
typedef struct {
@@ -121,7 +132,9 @@ typedef struct {
typedef struct {
char *text;
+ char *wrapped;
int32_t lineCount;
+ int32_t wrapWidth;
uint8_t noteType;
} HelpNoteDataT;
@@ -131,7 +144,10 @@ typedef struct {
} HelpCodeDataT;
typedef struct {
- char *text;
+ char *text;
+ char *wrapped;
+ int32_t lineCount;
+ int32_t wrapWidth;
} HelpListItemDataT;
// ============================================================
@@ -167,7 +183,6 @@ static void helpTextDestroy(WidgetT *w);
static void helpTextPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
static void historyPush(int32_t topicIdx);
static const char *hlpString(uint32_t offset);
-static int32_t maxLineWidth(const BitmapFontT *font, const char *text);
static void navigateBack(void);
static void navigateForward(void);
static void navigateToTopic(int32_t topicIdx);
@@ -200,16 +215,98 @@ AppDescriptorT appDescriptor = {
// Utility helpers
// ============================================================
-static int32_t countLines(const char *text) {
- int32_t count = 1;
+// Word-wrap flowing text to fit within colWidth characters.
+// Returns a dvxMalloc'd string with \n at wrap points.
+// Caller must dvxFree the result.
+static char *wrapText(const char *text, int32_t colWidth) {
+ if (!text || !text[0] || colWidth < 10) {
+ return dvxStrdup(text ? text : "");
+ }
- for (const char *p = text; *p; p++) {
+ int32_t textLen = (int32_t)strlen(text);
+ char *result = (char *)dvxMalloc(textLen + textLen / colWidth + 16);
+
+ if (!result) {
+ return dvxStrdup(text);
+ }
+
+ int32_t outPos = 0;
+ int32_t col = 0;
+ const char *p = text;
+
+ while (*p) {
+ // Skip leading whitespace at start of line
+ if (col == 0) {
+ while (*p == ' ') {
+ p++;
+ }
+ }
+
+ if (!*p) {
+ break;
+ }
+
+ // Find end of word
+ const char *wordStart = p;
+
+ while (*p && *p != ' ' && *p != '\n') {
+ p++;
+ }
+
+ int32_t wordLen = (int32_t)(p - wordStart);
+
+ // Check if word fits on current line
+ if (col > 0 && col + 1 + wordLen > colWidth) {
+ result[outPos++] = '\n';
+ col = 0;
+ } else if (col > 0) {
+ result[outPos++] = ' ';
+ col++;
+ }
+
+ memcpy(result + outPos, wordStart, wordLen);
+ outPos += wordLen;
+ col += wordLen;
+
+ // Skip spaces between words
+ while (*p == ' ') {
+ p++;
+ }
+
+ // Explicit newline in source = paragraph break
if (*p == '\n') {
- count++;
+ result[outPos++] = '\n';
+ col = 0;
+ p++;
}
}
- return count;
+ result[outPos] = '\0';
+ return result;
+}
+
+
+// Ensure a HelpText-style widget's wrapped text is up to date for the given pixel width.
+// Returns the wrapped text and updates lineCount. Uses cached result if width unchanged.
+// Wrap text at the given pixel width. Caches the result — only re-wraps
+// when pixelW changes. Returns the wrapped string.
+static const char *doWrap(char **wrappedPtr, int32_t *wrapWidthPtr, int32_t *lineCountPtr, const char *text, int32_t pixelW, int32_t charW, int32_t padPixels) {
+ int32_t cols = (pixelW - padPixels) / charW;
+
+ if (cols < 10) {
+ cols = 10;
+ }
+
+ // Return cached result if width hasn't changed
+ if (*wrappedPtr && *wrapWidthPtr == pixelW) {
+ return *wrappedPtr;
+ }
+
+ dvxFree(*wrappedPtr);
+ *wrappedPtr = wrapText(text, cols);
+ *wrapWidthPtr = pixelW;
+ *lineCountPtr = countLines(*wrappedPtr);
+ return *wrappedPtr;
}
@@ -239,6 +336,23 @@ static int32_t maxLineWidth(const BitmapFontT *font, const char *text) {
}
+
+
+static int32_t countLines(const char *text) {
+ int32_t count = 1;
+
+ for (const char *p = text; *p; p++) {
+ if (*p == '\n') {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+
+
+
static void paintLines(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t lineCount, uint32_t fg) {
const char *line = text;
@@ -259,16 +373,33 @@ static void paintLines(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font
// HelpText widget class
// ============================================================
+
+// Update sWrapWidth from the ScrollPane's current size.
+static void updateWrapWidth(void) {
+ // 38 = ScrollPane border (4) + VBox padding (12) + scrollbar (14) + 1 char buffer (8)
+ if (sContentScroll && sContentScroll->w > 38) {
+ sWrapWidth = sContentScroll->w - 38;
+ }
+}
+
+
static void helpTextCalcMinSize(WidgetT *w, const BitmapFontT *font) {
HelpTextDataT *td = (HelpTextDataT *)w->data;
- w->calcMinW = maxLineWidth(font, td->text) + HELP_CONTENT_PAD * 2;
+ updateWrapWidth();
+
+ if (sWrapWidth > 0) {
+ doWrap(&td->wrapped, &td->wrapWidth, &td->lineCount, td->text, sWrapWidth, font->charWidth, 0);
+ }
+
+ w->calcMinW = 0;
w->calcMinH = td->lineCount * font->charHeight;
}
static void helpTextPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
HelpTextDataT *td = (HelpTextDataT *)w->data;
- paintLines(d, ops, font, w->x + HELP_CONTENT_PAD, w->y, td->text, td->lineCount, colors->contentFg);
+ const char *text = td->wrapped ? td->wrapped : td->text;
+ paintLines(d, ops, font, w->x + HELP_CONTENT_PAD, w->y, text, td->lineCount, colors->contentFg);
}
@@ -276,6 +407,7 @@ static void helpTextDestroy(WidgetT *w) {
HelpTextDataT *td = (HelpTextDataT *)w->data;
if (td) {
+ dvxFree(td->wrapped);
dvxFree(td->text);
dvxFree(td);
w->data = NULL;
@@ -286,22 +418,27 @@ static void helpTextDestroy(WidgetT *w) {
// HelpHeading widget class
// ============================================================
+static bool isFirstChild(WidgetT *w) {
+ return w->parent && w->parent->firstChild == w;
+}
+
+
static void helpHeadingCalcMinSize(WidgetT *w, const BitmapFontT *font) {
HelpHeadingDataT *hd = (HelpHeadingDataT *)w->data;
- int32_t textW = textWidth(font, hd->text);
int32_t textH = font->charHeight;
+ bool first = isFirstChild(w);
- w->calcMinW = textW + HELP_CONTENT_PAD * 2;
+ w->calcMinW = 0;
if (hd->level == 1) {
- // Full bar with padding above and below
- w->calcMinH = textH + HELP_HEADING1_PAD * 2 + 2;
+ int32_t top = first ? 0 : HELP_HEADING1_TOP;
+ w->calcMinH = top + textH + HELP_HEADING1_PAD * 2;
} else if (hd->level == 2) {
- // Text with underline
- w->calcMinH = textH + HELP_HEADING2_PAD + 2;
+ int32_t top = first ? 0 : HELP_HEADING2_TOP;
+ w->calcMinH = top + textH + HELP_HEADING2_PAD + 2;
} else {
- // Plain bold text
- w->calcMinH = textH + 2;
+ int32_t top = first ? 0 : HELP_HEADING3_TOP;
+ w->calcMinH = top + textH;
}
}
@@ -309,21 +446,25 @@ static void helpHeadingCalcMinSize(WidgetT *w, const BitmapFontT *font) {
static void helpHeadingPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
HelpHeadingDataT *hd = (HelpHeadingDataT *)w->data;
int32_t textLen = (int32_t)strlen(hd->text);
+ bool first = isFirstChild(w);
if (hd->level == 1) {
- // Filled bar with title bar colors
- rectFill(d, ops, w->x, w->y, w->w, w->h, colors->activeTitleBg);
- int32_t textY = w->y + HELP_HEADING1_PAD;
+ int32_t top = first ? 0 : HELP_HEADING1_TOP;
+ int32_t barY = w->y + top;
+ int32_t barH = w->h - top;
+ rectFill(d, ops, w->x, barY, w->w, barH, colors->activeTitleBg);
+ int32_t textY = barY + HELP_HEADING1_PAD;
drawTextN(d, ops, font, w->x + HELP_CONTENT_PAD, textY, hd->text, textLen, colors->activeTitleFg, 0, false);
} else if (hd->level == 2) {
- // Text with underline
- int32_t textY = w->y;
+ int32_t top = first ? 0 : HELP_HEADING2_TOP;
+ int32_t textY = w->y + top;
drawTextN(d, ops, font, w->x + HELP_CONTENT_PAD, textY, hd->text, textLen, colors->contentFg, 0, false);
int32_t lineY = textY + font->charHeight + 1;
drawHLine(d, ops, w->x + HELP_CONTENT_PAD, lineY, w->w - HELP_CONTENT_PAD * 2, colors->windowShadow);
} else {
- // Plain text
- drawTextN(d, ops, font, w->x + HELP_CONTENT_PAD, w->y, hd->text, textLen, colors->contentFg, 0, false);
+ int32_t top = first ? 0 : HELP_HEADING3_TOP;
+ int32_t textY = w->y + top;
+ drawTextN(d, ops, font, w->x + HELP_CONTENT_PAD, textY, hd->text, textLen, colors->contentFg, 0, false);
}
}
@@ -343,8 +484,7 @@ static void helpHeadingDestroy(WidgetT *w) {
// ============================================================
static void helpLinkCalcMinSize(WidgetT *w, const BitmapFontT *font) {
- HelpLinkDataT *ld = (HelpLinkDataT *)w->data;
- w->calcMinW = textWidth(font, ld->displayText) + HELP_CONTENT_PAD * 2 + HELP_LINK_PAD_X * 2;
+ w->calcMinW = 0;
w->calcMinH = font->charHeight + HELP_LINK_PAD_Y * 2;
}
@@ -403,7 +543,14 @@ static void helpLinkDestroy(WidgetT *w) {
static void helpNoteCalcMinSize(WidgetT *w, const BitmapFontT *font) {
HelpNoteDataT *nd = (HelpNoteDataT *)w->data;
- w->calcMinW = maxLineWidth(font, nd->text) + HELP_CONTENT_PAD * 2 + HELP_NOTE_PAD * 2 + HELP_NOTE_PREFIX_W;
+ updateWrapWidth();
+
+ if (sWrapWidth > 0) {
+ int32_t notePad = HELP_NOTE_PAD * 2 + HELP_NOTE_PREFIX_W;
+ doWrap(&nd->wrapped, &nd->wrapWidth, &nd->lineCount, nd->text, sWrapWidth, font->charWidth, notePad);
+ }
+
+ w->calcMinW = 0;
w->calcMinH = nd->lineCount * font->charHeight + HELP_NOTE_PAD * 2;
}
@@ -411,7 +558,6 @@ static void helpNoteCalcMinSize(WidgetT *w, const BitmapFontT *font) {
static void helpNotePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
HelpNoteDataT *nd = (HelpNoteDataT *)w->data;
- // Left accent bar color varies by note type; background is always windowFace
uint32_t barColor;
if (nd->noteType == HLP_NOTE_WARNING) {
@@ -429,7 +575,8 @@ static void helpNotePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
rectFill(d, ops, boxX, w->y, HELP_NOTE_PREFIX_W, w->h, barColor);
int32_t textX = boxX + HELP_NOTE_PREFIX_W + HELP_NOTE_PAD;
- paintLines(d, ops, font, textX, w->y + HELP_NOTE_PAD, nd->text, nd->lineCount, colors->contentFg);
+ const char *text = nd->wrapped ? nd->wrapped : nd->text;
+ paintLines(d, ops, font, textX, w->y + HELP_NOTE_PAD, text, nd->lineCount, colors->contentFg);
}
@@ -437,6 +584,7 @@ static void helpNoteDestroy(WidgetT *w) {
HelpNoteDataT *nd = (HelpNoteDataT *)w->data;
if (nd) {
+ dvxFree(nd->wrapped);
dvxFree(nd->text);
dvxFree(nd);
w->data = NULL;
@@ -503,24 +651,21 @@ static void helpRulePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
static void helpListItemCalcMinSize(WidgetT *w, const BitmapFontT *font) {
HelpListItemDataT *ld = (HelpListItemDataT *)w->data;
- w->calcMinW = textWidth(font, ld->text) + HELP_CONTENT_PAD * 2 + HELP_LIST_INDENT;
- w->calcMinH = font->charHeight;
+ updateWrapWidth();
+
+ if (sWrapWidth > 0) {
+ doWrap(&ld->wrapped, &ld->wrapWidth, &ld->lineCount, ld->text, sWrapWidth, font->charWidth, 0);
+ }
+
+ w->calcMinW = 0;
+ w->calcMinH = ld->lineCount * font->charHeight;
}
static void helpListItemPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
HelpListItemDataT *ld = (HelpListItemDataT *)w->data;
- int32_t textLen = (int32_t)strlen(ld->text);
- int32_t bulletX = w->x + HELP_CONTENT_PAD + HELP_LIST_INDENT - HELP_LIST_BULLET_W;
- int32_t textX = w->x + HELP_CONTENT_PAD + HELP_LIST_INDENT;
- int32_t textY = w->y;
-
- // Bullet character
- drawTextN(d, ops, font, bulletX, textY, "\x07", 1, colors->contentFg, 0, false);
-
- if (textLen > 0) {
- drawTextN(d, ops, font, textX, textY, ld->text, textLen, colors->contentFg, 0, false);
- }
+ const char *text = ld->wrapped ? ld->wrapped : ld->text;
+ paintLines(d, ops, font, w->x + HELP_CONTENT_PAD, w->y, text, ld->lineCount, colors->contentFg);
}
@@ -528,6 +673,7 @@ static void helpListItemDestroy(WidgetT *w) {
HelpListItemDataT *ld = (HelpListItemDataT *)w->data;
if (ld) {
+ dvxFree(ld->wrapped);
dvxFree(ld->text);
dvxFree(ld);
w->data = NULL;
@@ -697,15 +843,38 @@ static void navigateForward(void) {
}
+// userData stores topicIdx + 1 so that topic 0 doesn't collide with NULL
+#define TOPIC_TO_USERDATA(idx) ((void *)(intptr_t)((idx) + 1))
+#define USERDATA_TO_TOPIC(ud) ((int32_t)(intptr_t)(ud) - 1)
+
+static WidgetT *findTreeItemByTopic(WidgetT *node, int32_t topicIdx) {
+ for (WidgetT *child = node->firstChild; child; child = child->nextSibling) {
+ if (child->userData && USERDATA_TO_TOPIC(child->userData) == topicIdx) {
+ return child;
+ }
+
+ WidgetT *found = findTreeItemByTopic(child, topicIdx);
+
+ if (found) {
+ return found;
+ }
+ }
+
+ return NULL;
+}
+
+
static void navigateToTopic(int32_t topicIdx) {
if (topicIdx < 0 || (uint32_t)topicIdx >= sHeader.topicCount) {
return;
}
if (topicIdx == sCurrentTopic) {
+ dvxLog("[WRAP] navigateToTopic: SKIPPED (same topic %d)", (int)topicIdx);
return;
}
+ dvxLog("[WRAP] navigateToTopic: %d -> %d", (int)sCurrentTopic, (int)topicIdx);
historyPush(topicIdx);
sCurrentTopic = topicIdx;
buildContentWidgets();
@@ -716,6 +885,20 @@ static void navigateToTopic(int32_t topicIdx) {
char winTitle[300];
snprintf(winTitle, sizeof(winTitle), "%s - DVX Help", title);
dvxSetTitle(sAc, sWin, winTitle);
+
+ // Sync TOC tree selection
+ if (sTocTree) {
+ WidgetT *item = findTreeItemByTopic(sTocTree, topicIdx);
+ dvxLog("[WRAP] treeSync: topicIdx=%d item=%p", (int)topicIdx, (void *)item);
+
+ if (item) {
+ // Suppress onChange to avoid re-entering navigateToTopic
+ void (*savedOnChange)(WidgetT *) = sTocTree->onChange;
+ sTocTree->onChange = NULL;
+ wgtTreeViewSetSelected(sTocTree, item);
+ sTocTree->onChange = savedOnChange;
+ }
+ }
}
@@ -745,7 +928,7 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
if (w) {
HelpTextDataT *td = (HelpTextDataT *)dvxCalloc(1, sizeof(HelpTextDataT));
td->text = dvxStrdup(payload);
- td->lineCount = countLines(td->text);
+ td->wrapWidth = -1;
w->data = td;
}
@@ -768,9 +951,9 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
}
case HLP_REC_LINK: {
- // Payload: display text \0 target topic ID \0
- const char *displayText = payload;
- const char *targetTopicId = payload + strlen(payload) + 1;
+ // Payload: target topic ID \0 display text \0
+ const char *targetTopicId = payload;
+ const char *displayText = payload + strlen(payload) + 1;
WidgetT *w = widgetAlloc(sContentBox, sHelpLinkTypeId);
if (w) {
@@ -819,8 +1002,18 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
if (w) {
HelpListItemDataT *ld = (HelpListItemDataT *)dvxCalloc(1, sizeof(HelpListItemDataT));
- ld->text = dvxStrdup(payload);
- w->data = ld;
+ // Prepend bullet + space to the text
+ int32_t pLen = (int32_t)strlen(payload);
+ ld->text = (char *)dvxMalloc(pLen + 3);
+
+ if (ld->text) {
+ ld->text[0] = '\x07';
+ ld->text[1] = ' ';
+ memcpy(ld->text + 2, payload, pLen + 1);
+ }
+
+ ld->wrapWidth = -1;
+ w->data = ld;
}
break;
@@ -837,7 +1030,7 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
if (w) {
HelpNoteDataT *nd = (HelpNoteDataT *)dvxCalloc(1, sizeof(HelpNoteDataT));
nd->text = dvxStrdup(payload);
- nd->lineCount = countLines(nd->text);
+ nd->wrapWidth = -1;
nd->noteType = hdr->flags;
w->data = nd;
}
@@ -875,6 +1068,8 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
}
+// Re-wrap all text content widgets at the current content box width.
+
static void buildContentWidgets(void) {
if (!sContentBox || sCurrentTopic < 0) {
return;
@@ -927,12 +1122,25 @@ static void buildContentWidgets(void) {
dvxFree(payload);
}
- // Scroll to top by scrolling the first child into view
- if (sContentScroll && sContentBox->firstChild) {
- wgtScrollPaneScrollToChild(sContentScroll, sContentBox->firstChild);
+ // Compute viewport wrap width from the ScrollPane.
+ // ScrollPane border = 2*2 = 4, vertical scrollbar = 14, VBox padding = 2*6 = 12.
+ // Total overhead = 30. This is approximate but stable.
+ if (sContentScroll && sContentScroll->w > 30) {
+ updateWrapWidth();
}
- wgtInvalidate(sContentBox);
+ // Reset scroll to top
+ if (sContentScroll) {
+ wgtScrollPaneScrollToTop(sContentScroll);
+ }
+
+ // Two invalidate passes: the first does measure+layout which may change
+ // scrollbar visibility (altering available width). The second re-measures
+ // at the corrected width so heights match the actual layout.
+ if (sContentScroll) {
+ wgtInvalidate(sContentScroll);
+ wgtInvalidate(sContentScroll);
+ }
}
// ============================================================
@@ -965,8 +1173,8 @@ static void populateToc(void) {
WidgetT *item = wgtTreeItem(parent, title);
if (item) {
- // Store topic index in userData for selection callback
- item->userData = (void *)(intptr_t)entry->topicIdx;
+ // Store topic index in userData (+1 so topic 0 != NULL)
+ item->userData = TOPIC_TO_USERDATA(entry->topicIdx);
// Update parent for next deeper level
if (depth + 1 < MAX_TOC_DEPTH) {
@@ -1168,6 +1376,12 @@ static void onMenu(WindowT *win, int32_t menuId) {
case CMD_SEARCH:
onSearch(NULL);
break;
+
+ case CMD_EXIT:
+ if (sWin) {
+ onClose(sWin);
+ }
+ break;
}
}
@@ -1262,13 +1476,13 @@ static void onSearch(WidgetT *w) {
static void onTocChange(WidgetT *w) {
WidgetT *selected = wgtTreeViewGetSelected(w);
- if (!selected) {
+ if (!selected || !selected->userData) {
return;
}
- int32_t topicIdx = (int32_t)(intptr_t)selected->userData;
+ int32_t topicIdx = USERDATA_TO_TOPIC(selected->userData);
- if (topicIdx == 0xFFFF) {
+ if (topicIdx < 0 || topicIdx >= (int32_t)sHeader.topicCount) {
return;
}
@@ -1324,8 +1538,8 @@ int32_t appMain(DxeAppContextT *ctx) {
return -1;
}
- sWin->onClose = onClose;
- sWin->onMenu = onMenu;
+ sWin->onClose = onClose;
+ sWin->onMenu = onMenu;
// Menu bar
MenuBarT *menuBar = wmAddMenuBar(sWin);
@@ -1336,6 +1550,8 @@ int32_t appMain(DxeAppContextT *ctx) {
wmAddMenuSeparator(navMenu);
wmAddMenuItem(navMenu, "&Index...", CMD_INDEX);
wmAddMenuItem(navMenu, "&Search...", CMD_SEARCH);
+ wmAddMenuSeparator(navMenu);
+ wmAddMenuItem(navMenu, "E&xit", CMD_EXIT);
// Widget tree
WidgetT *root = wgtInitWindow(sAc, sWin);
@@ -1370,7 +1586,7 @@ int32_t appMain(DxeAppContextT *ctx) {
sContentScroll->weight = 100;
sContentBox = wgtVBox(sContentScroll);
- sContentBox->spacing = wgtPixels(2);
+ sContentBox->spacing = wgtPixels(HELP_PARA_SPACING);
sContentBox->padding = wgtPixels(HELP_CONTENT_PAD);
wgtSplitterSetPos(splitter, HELP_SPLITTER_POS);
diff --git a/apps/dvxhelp/help.dhs b/apps/dvxhelp/help.dhs
new file mode 100644
index 0000000..4fc3bbf
--- /dev/null
+++ b/apps/dvxhelp/help.dhs
@@ -0,0 +1,216 @@
+.topic help.overview
+.title DVX Help Viewer
+.toc 0 DVX Help Viewer
+.default
+.index DVX Help
+.index Help Viewer
+
+.h1 DVX Help Viewer
+
+The DVX Help Viewer displays .hlp help files compiled from .dhs source documents. It provides a tree-based table of contents, scrollable content with word-wrapped text, clickable hyperlinks, full-text search, and a keyword index.
+
+.h2 Opening Help
+
+Press F1 from any DVX application to open context-sensitive help. Applications can register their own help file and topic so F1 opens the relevant page.
+
+You can also launch the help viewer from an application's Help menu, or by clicking the DVX Help icon in the Program Manager.
+
+.h2 Navigation
+
+.list
+.item Click a topic in the tree on the left to display it
+.item Click underlined links in the content to jump to other topics
+.item Use the Back and Forward buttons (or Navigate menu) to retrace your steps
+.item Use Navigate > Index to browse an alphabetical keyword list
+.item Use Navigate > Search to find topics by keyword
+.endlist
+
+.h2 Keyboard Shortcuts
+
+.table
+ Alt+Left Back
+ Alt+Right Forward
+ Ctrl+F Search
+ Escape Close viewer
+.endtable
+
+.topic help.format
+.title Help Source Format (.dhs)
+.toc 0 Help Source Format
+.index .dhs
+.index Source Format
+.index Directives
+
+.h1 Help Source Format (.dhs)
+
+Help files are authored as plain text .dhs source files using a simple line-oriented directive format. Lines beginning with a period at column 0 are directives. All other lines are body text, which is automatically word-wrapped by the viewer at display time.
+
+.h2 Topic Directives
+
+.table
+ .topic Start a new topic with a unique string ID
+ .title Set the topic's display title
+ .toc Add a table of contents entry (0=root, 1=child, etc.)
+ .default Mark this topic as the one shown when the file opens
+.endtable
+
+.h2 Content Directives
+
+.table
+ .h1 Level 1 heading (colored bar)
+ .h2 Level 2 heading (underlined)
+ .h3 Level 3 heading (plain)
+ .hr Horizontal rule
+ .link Hyperlink to another topic
+ .image Inline image (BMP format)
+.endtable
+
+.h2 Block Directives
+
+.table
+ .list Start a bulleted list
+ .item List item (must be inside .list)
+ .endlist End the bulleted list
+ .table Start a preformatted table block
+ .endtable End table block
+ .code Start a preformatted code block
+ .endcode End code block
+ .note [info|tip|warning] Start a callout box
+ .endnote End callout box
+.endtable
+
+.h2 Index Directives
+
+.table
+ .index Add a keyword to the index pointing to this topic
+.endtable
+
+.h2 Example
+
+.code
+.topic intro
+.title Welcome
+.toc 0 Welcome
+.default
+.index Welcome
+
+.h1 Welcome
+
+This is a paragraph of body text. It will be
+automatically word-wrapped by the viewer.
+
+.list
+.item First item
+.item Second item
+.endlist
+
+.link other.topic See also: Other Topic
+
+.note info
+This is an informational note.
+.endnote
+
+.note tip
+This is a helpful tip.
+.endnote
+
+.note warning
+This is a warning message.
+.endnote
+.endcode
+
+.h2 Callout Boxes
+
+Three types of callout boxes are available, each with a distinct colored accent bar:
+
+.note info
+Use info notes for general supplementary information.
+.endnote
+
+.note tip
+Use tip notes for helpful suggestions and best practices.
+.endnote
+
+.note warning
+Use warning notes for important cautions the reader should be aware of.
+.endnote
+
+.topic help.compiler
+.title Help Compiler (dvxhlpc)
+.toc 0 Help Compiler
+.index dvxhlpc
+.index Compiler
+
+.h1 Help Compiler (dvxhlpc)
+
+The dvxhlpc tool runs on the host (Linux) and compiles .dhs source files into binary .hlp files for the viewer, and optionally into self-contained HTML.
+
+.h2 Usage
+
+.code
+dvxhlpc -o output.hlp [-i imagedir] [--html out.html] input.dhs [...]
+.endcode
+
+.h2 Options
+
+.table
+ -o output.hlp Output binary help file (required)
+ -i imagedir Directory to find .image files (default: current dir)
+ --html out.html Also emit a self-contained HTML file
+.endtable
+
+Multiple input files are merged into a single help file. This allows per-widget or per-feature documentation fragments to be combined automatically.
+
+.h2 Build Integration
+
+The standard build pattern globs all fragments:
+
+.code
+dvxhlpc -o dvxhelp.hlp docs/src/overview.dhs widgets/*/*.dhs
+.endcode
+
+New widgets or features just drop a .dhs file in their source directory and it appears in the help on the next build.
+
+.h2 HTML Output
+
+The --html flag produces a single self-contained HTML file with a sidebar table of contents, styled headings, lists, code blocks, notes, and embedded images (base64 data URIs). This is useful for viewing documentation on the host machine without running the DOS help viewer.
+
+.topic help.integration
+.title Application Integration
+.toc 0 Application Integration
+.index F1
+.index Context Help
+.index helpFile
+.index helpTopic
+.index shellLoadAppWithArgs
+
+.h1 Application Integration
+
+Any DVX application can provide context-sensitive help via the F1 key.
+
+.h2 Setting Up Help
+
+In your appMain, set the help file path on the app context:
+
+.code
+snprintf(ctx->helpFile, sizeof(ctx->helpFile),
+ "%s%cMYAPP.HLP", ctx->appDir, DVX_PATH_SEP);
+.endcode
+
+.h2 Context-Sensitive Topics
+
+Update helpTopic as the user navigates your application:
+
+.code
+snprintf(ctx->helpTopic, sizeof(ctx->helpTopic), "settings.video");
+.endcode
+
+When the user presses F1, the shell launches the help viewer with your help file opened to the specified topic.
+
+.h2 Launching Help from Menus
+
+To add a Help menu item that opens your help file:
+
+.code
+shellLoadAppWithArgs(ctx, viewerPath, helpFilePath);
+.endcode
diff --git a/apps/dvxhelp/hlpformat.h b/apps/dvxhelp/hlpformat.h
index b78d444..3ad4c93 100644
--- a/apps/dvxhelp/hlpformat.h
+++ b/apps/dvxhelp/hlpformat.h
@@ -71,7 +71,6 @@ typedef struct {
uint32_t imagePoolOffset;
uint32_t imagePoolSize;
uint32_t defaultTopicStr; // string table offset of default topic ID
- uint32_t wrapWidth; // column width text was pre-wrapped at
} HlpHeaderT;
diff --git a/apps/dvxhelp/sample.dvxhelp b/apps/dvxhelp/sample.dhs
similarity index 100%
rename from apps/dvxhelp/sample.dvxhelp
rename to apps/dvxhelp/sample.dhs
diff --git a/apps/progman/progman.c b/apps/progman/progman.c
index 0bbb2ec..39506c8 100644
--- a/apps/progman/progman.c
+++ b/apps/progman/progman.c
@@ -76,6 +76,7 @@
#define CMD_ABOUT 300
#define CMD_TASK_MGR 301
#define CMD_SYSINFO 302
+#define CMD_HELP 303
// ============================================================
// Module state
@@ -179,6 +180,8 @@ static void buildPmWindow(void) {
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
+ wmAddMenuItem(helpMenu, "&DVX Help\tF1", CMD_HELP);
+ wmAddMenuSeparator(helpMenu);
wmAddMenuItem(helpMenu, "&About DVX...", CMD_ABOUT);
wmAddMenuItem(helpMenu, "&System Information...", CMD_SYSINFO);
wmAddMenuSeparator(helpMenu);
@@ -360,6 +363,15 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
prefsSave(sPrefs);
break;
+ case CMD_HELP: {
+ char viewerPath[DVX_MAX_PATH];
+ char sysHlp[DVX_MAX_PATH];
+ snprintf(viewerPath, sizeof(viewerPath), "APPS%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP);
+ snprintf(sysHlp, sizeof(sysHlp), "%s%c%s", sCtx->appDir, DVX_PATH_SEP, "dvxhelp.hlp");
+ shellLoadAppWithArgs(sAc, viewerPath, sysHlp);
+ break;
+ }
+
case CMD_ABOUT:
showAboutDialog();
break;
@@ -555,6 +567,9 @@ int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
sAc = ctx->shellCtx;
+ // Set help file for F1 — system help lives in progman's app directory
+ snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, "dvxhelp.hlp");
+
// Load saved preferences
char prefsPath[DVX_MAX_PATH];
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
diff --git a/docs/src/dvx_api_reference.dvxhelp b/core/apiref.dhs
similarity index 99%
rename from docs/src/dvx_api_reference.dvxhelp
rename to core/apiref.dhs
index f7dc2c9..cbddf23 100644
--- a/docs/src/dvx_api_reference.dvxhelp
+++ b/core/apiref.dhs
@@ -35,7 +35,7 @@ The DVX GUI is built as a five-layer architecture. Each layer is defined in its
.topic api.types
.title dvxTypes.h -- Shared Type Definitions
-.toc 0 dvxTypes.h -- Shared Type Definitions
+.toc 1 dvxTypes.h -- Shared Type Definitions
.index dvxTypes.h
.index PixelFormatT
.index DisplayT
@@ -412,7 +412,7 @@ Software-rendered 16x16 cursor using AND/XOR mask encoding.
.topic api.cursor
.title dvxCursor.h -- Cursor Definitions
-.toc 0 dvxCursor.h -- Cursor Definitions
+.toc 1 dvxCursor.h -- Cursor Definitions
.index dvxCursor.h
.index Cursor Shapes
.index CURSOR_ARROW
@@ -445,7 +445,7 @@ Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each ent
.topic api.video
.title dvxVideo.h -- Layer 1: VESA VBE Video Backend
-.toc 0 dvxVideo.h -- Layer 1: Video Backend
+.toc 1 dvxVideo.h -- Layer 1: Video Backend
.index dvxVideo.h
.index videoInit
.index videoShutdown
@@ -539,7 +539,7 @@ Reset the clip rectangle to the full display dimensions.
.topic api.draw
.title dvxDraw.h -- Layer 2: Drawing Primitives
-.toc 0 dvxDraw.h -- Layer 2: Drawing Primitives
+.toc 1 dvxDraw.h -- Layer 2: Drawing Primitives
.index dvxDraw.h
.index drawInit
.index rectFill
@@ -881,7 +881,7 @@ Draw a vertical line (1px wide).
.topic api.comp
.title dvxComp.h -- Layer 3: Dirty Rectangle Compositor
-.toc 0 dvxComp.h -- Layer 3: Compositor
+.toc 1 dvxComp.h -- Layer 3: Compositor
.index dvxComp.h
.index dirtyListInit
.index dirtyListAdd
@@ -1002,7 +1002,7 @@ Returns: true if w <= 0 or h <= 0.
.topic api.wm
.title dvxWm.h -- Layer 4: Window Manager
-.toc 0 dvxWm.h -- Layer 4: Window Manager
+.toc 1 dvxWm.h -- Layer 4: Window Manager
.index dvxWm.h
.index wmInit
.index wmCreateWindow
@@ -1823,7 +1823,7 @@ Returns: 0 on success, -1 on failure.
.topic api.app
.title dvxApp.h -- Layer 5: Application API
-.toc 0 dvxApp.h -- Layer 5: Application API
+.toc 1 dvxApp.h -- Layer 5: Application API
.index dvxApp.h
.index AppContextT
.index dvxInit
@@ -2869,7 +2869,7 @@ Returns: 32-bit hash value.
.topic api.widget
.title dvxWidget.h -- Widget System
-.toc 0 dvxWidget.h -- Widget System
+.toc 1 dvxWidget.h -- Widget System
.index dvxWidget.h
.index WidgetT
.index WidgetClassT
diff --git a/docs/src/dvx_architecture.dvxhelp b/core/arch.dhs
similarity index 97%
rename from docs/src/dvx_architecture.dvxhelp
rename to core/arch.dhs
index 210253b..54cc870 100644
--- a/docs/src/dvx_architecture.dvxhelp
+++ b/core/arch.dhs
@@ -27,23 +27,20 @@ The runtime environment consists of a bootstrap loader (dvx.exe) that loads core
.h2 Contents
-.list
-.item .link arch.overview System Overview
-.item .link arch.layers Five-Layer Architecture
-.item .link arch.pipeline Display Pipeline
-.item .link arch.windows Window System
-.item .link arch.widgets Widget System
-.item .link arch.dxe DXE Module System
-.item .link arch.events Event Model
-.item .link arch.fonts Font System
-.item .link arch.colors Color System
-.item .link arch.platform Platform Layer
-.item .link arch.build Build System
-.endlist
+.link arch.layers Five-Layer Architecture
+.link arch.pipeline Display Pipeline
+.link arch.windows Window System
+.link arch.widgets Widget System
+.link arch.dxe DXE Module System
+.link arch.events Event Model
+.link arch.fonts Font System
+.link arch.colors Color System
+.link arch.platform Platform Layer
+.link arch.build Build System
.topic arch.layers
.title Five-Layer Architecture
-.toc 0 Five-Layer Architecture
+.toc 1 Five-Layer Architecture
.index Layers
.index dvxVideo
.index dvxDraw
@@ -102,7 +99,7 @@ DVX is organized into five layers, each implemented as a single .h/.c pair. Ever
.topic arch.pipeline
.title Display Pipeline
-.toc 0 Display Pipeline
+.toc 1 Display Pipeline
.index Display Pipeline
.index Backbuffer
.index Linear Framebuffer
@@ -166,7 +163,7 @@ DirtyListT -- Fixed-capacity dynamic array of RectT. Linear scanning for merge c
.topic arch.windows
.title Window System
-.toc 0 Window System
+.toc 1 Window System
.index Window
.index WindowT
.index Z-Order
@@ -258,7 +255,7 @@ Minimized windows display as 64x64 icons at the bottom of the screen with bevele
.topic arch.widgets
.title Widget System
-.toc 0 Widget System
+.toc 1 Widget System
.index Widgets
.index WidgetT
.index WidgetClassT
@@ -360,7 +357,7 @@ Each widget can register an interface descriptor that describes its BASIC-facing
.topic arch.dxe
.title DXE Module System
-.toc 0 DXE Module System
+.toc 1 DXE Module System
.index DXE
.index DXE3
.index Modules
@@ -433,7 +430,7 @@ All allocations route through dvxMalloc/dvxFree wrappers that prepend a 16-byte
.topic arch.events
.title Event Model
-.toc 0 Event Model
+.toc 1 Event Model
.index Events
.index Input
.index Mouse
@@ -497,7 +494,7 @@ Timestamp-based: two clicks on the same target (title bar, minimized icon, close
.topic arch.fonts
.title Font System
-.toc 0 Font System
+.toc 1 Font System
.index Fonts
.index Bitmap Font
.index BitmapFontT
@@ -546,7 +543,7 @@ AppContextT stores a fixed-point 16.16 reciprocal of font.charHeight (charHeight
.topic arch.colors
.title Color System
-.toc 0 Color System
+.toc 1 Color System
.index Colors
.index Pixel Format
.index PixelFormatT
@@ -603,7 +600,7 @@ Bevels are the defining visual element of the Motif aesthetic. Convenience macro
.topic arch.platform
.title Platform Layer
-.toc 0 Platform Layer
+.toc 1 Platform Layer
.index Platform Layer
.index dvxPlatform
.index VESA
@@ -658,7 +655,7 @@ platformRegisterDxeExports() -- register C runtime and platform symbols for DXE
.topic arch.build
.title Build System
-.toc 0 Build System
+.toc 1 Build System
.index Build
.index Makefile
.index Cross-Compilation
diff --git a/docs/dvx_api_reference.html b/docs/dvx_api_reference.html
index 3818b7e..7fa4c5e 100644
--- a/docs/dvx_api_reference.html
+++ b/docs/dvx_api_reference.html
@@ -29,7 +29,8 @@ img { max-width: 100%; }
Base WidgetT (Common Properties, Events, and Operations)
+
DVX Widget System
+
Complete reference for the DVX GUI widget toolkit. All widgets are implemented as dynamically loaded DXE modules. They are created via convenience macros that wrap the per-widget API function tables. The base WidgetT structure is defined in core/dvxWidget.h; individual widget headers live in widgets/.
+
Individual widgets are documented in their own sections. See the table of contents for the full list.
+
Base WidgetT (Common Properties, Events, and Operations)
+
Every widget inherits from the WidgetT structure defined in core/dvxWidget.h. The fields and callbacks listed here are available on all widget types.
+
Common Properties
+
Field Type Description
+ ----- ---- -----------
+ name char[32] Widget name for lookup via wgtFind().
+ x, y, w, h int32_t Computed geometry relative to the window content area (set by layout).
+ minW, minH int32_t (tagged) Minimum size hints. Use wgtPixels(), wgtChars(), or wgtPercent(). 0 = auto.
+ maxW, maxH int32_t (tagged) Maximum size constraints. 0 = no limit.
+ prefW, prefH int32_t (tagged) Preferred size. 0 = auto.
+ weight int32_t Extra-space distribution weight. 0 = fixed, 100 = normal. A widget with weight=200 gets twice the extra space of one with weight=100.
+ align WidgetAlignE Main-axis alignment for children: AlignStartE, AlignCenterE, AlignEndE.
+ spacing int32_t (tagged) Spacing between children (containers only). 0 = default.
+ padding int32_t (tagged) Internal padding (containers only). 0 = default.
+ fgColor uint32_t Foreground color override. 0 = use color scheme default.
+ bgColor uint32_t Background color override. 0 = use color scheme default.
+ visible bool Visibility state.
+ enabled bool Enabled state. Disabled widgets are grayed out and ignore input.
+ readOnly bool Read-only mode: allows scrolling/selection but blocks editing.
+ swallowTab bool When true, Tab key goes to the widget instead of navigating focus.
+ accelKey char Lowercase accelerator character. 0 if none.
+ tooltip const char * Tooltip text. NULL = none. Caller owns the string.
+ contextMenu MenuT * Right-click context menu. NULL = none. Caller owns.
+ userData void * Application-defined user data pointer.
+
Size Specification Macros
+
Macro Description
+ ----- -----------
+ wgtPixels(v) Size in pixels.
+ wgtChars(v) Size in character widths (multiplied by font charWidth).
+ wgtPercent(v) Size as a percentage of parent dimension.
+
Common Events (Callbacks)
+
These callback function pointers are available on every WidgetT. Set them directly on the widget struct.
+
Callback Signature Description
+ -------- --------- -----------
+ onClick void (*)(WidgetT *w) Fires on mouse click / activation.
+ onDblClick void (*)(WidgetT *w) Fires on double-click.
+ onChange void (*)(WidgetT *w) Fires when the widget's value changes (text, selection, check state, etc.).
+ onFocus void (*)(WidgetT *w) Fires when the widget receives keyboard focus.
+ onBlur void (*)(WidgetT *w) Fires when the widget loses keyboard focus.
+ onKeyPress void (*)(WidgetT *w, int32_t keyAscii) Fires on a printable key press (ASCII value).
+ onKeyDown void (*)(WidgetT *w, int32_t keyCode, int32_t shift) Fires on key down (scan code + shift state).
+ onKeyUp void (*)(WidgetT *w, int32_t keyCode, int32_t shift) Fires on key up.
+ onMouseDown void (*)(WidgetT *w, int32_t button, int32_t x, int32_t y) Fires on mouse button press.
+ onMouseUp void (*)(WidgetT *w, int32_t button, int32_t x, int32_t y) Fires on mouse button release.
+ onMouseMove void (*)(WidgetT *w, int32_t button, int32_t x, int32_t y) Fires on mouse movement over the widget.
+ onScroll void (*)(WidgetT *w, int32_t delta) Fires on mouse wheel scroll.
+ onValidate bool (*)(WidgetT *w) Validation callback. Return false to cancel a pending write.
+
Common Operations
+
Function Description
+ -------- -----------
+ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) Initialize widgets for a window. Returns the root VBox container.
+ AppContextT *wgtGetContext(const WidgetT *w) Walk up from any widget to retrieve the AppContextT.
+ void wgtInvalidate(WidgetT *w) Mark widget for re-layout and repaint. Propagates to ancestors.
+ void wgtInvalidatePaint(WidgetT *w) Mark widget for repaint only (no layout recalculation).
+ void wgtSetText(WidgetT *w, const char *text) Set widget text (label, button, textinput, etc.).
+ const char *wgtGetText(const WidgetT *w) Get widget text.
+ void wgtSetEnabled(WidgetT *w, bool enabled) Enable or disable a widget.
+ void wgtSetReadOnly(WidgetT *w, bool readOnly) Set read-only mode.
+ void wgtSetFocused(WidgetT *w) Set keyboard focus to a widget.
+ WidgetT *wgtGetFocused(void) Get the currently focused widget.
+ void wgtSetVisible(WidgetT *w, bool visible) Show or hide a widget.
+ void wgtSetName(WidgetT *w, const char *name) Set widget name for lookup.
+ WidgetT *wgtFind(WidgetT *root, const char *name) Find a widget by name in the subtree.
+ void wgtDestroy(WidgetT *w) Destroy a widget and all its children.
+ void wgtSetTooltip(WidgetT *w, const char *text) Set tooltip text. Pass NULL to remove.